@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.
- package/dist/GlobalDebugOverlay.d.ts +4 -0
- package/dist/Panel.d.ts +13 -0
- package/dist/{src/PanelDebugInfo.d.ts → PanelDebugInfo.d.ts} +2 -14
- package/dist/PanelGroup.d.ts +2 -0
- package/dist/PanelResizeHandle.d.ts +2 -0
- package/dist/allocateLayout.d.ts +12 -0
- package/dist/context.d.ts +3 -0
- package/dist/debugOverlayStore.d.ts +32 -0
- package/dist/{src/hooks.d.ts → hooks.d.ts} +8 -23
- package/dist/index.d.ts +13 -2
- package/dist/reducer.d.ts +8 -0
- package/dist/roundHalfToEven.d.ts +1 -0
- package/dist/types.d.ts +183 -0
- package/dist/utils/simple-logger.d.ts +37 -0
- package/dist/utils.d.ts +16 -0
- package/package.json +2 -3
- package/dist/src/GlobalDebugOverlay.d.ts +0 -21
- package/dist/src/GlobalDebugOverlay.d.ts.map +0 -1
- package/dist/src/Panel.d.ts +0 -46
- package/dist/src/Panel.d.ts.map +0 -1
- package/dist/src/PanelDebugInfo.d.ts.map +0 -1
- package/dist/src/PanelGroup.d.ts +0 -7
- package/dist/src/PanelGroup.d.ts.map +0 -1
- package/dist/src/PanelResizeHandle.d.ts +0 -7
- package/dist/src/PanelResizeHandle.d.ts.map +0 -1
- package/dist/src/allocateLayout.d.ts +0 -60
- package/dist/src/allocateLayout.d.ts.map +0 -1
- package/dist/src/context.d.ts +0 -12
- package/dist/src/context.d.ts.map +0 -1
- package/dist/src/debugOverlayStore.d.ts +0 -69
- package/dist/src/debugOverlayStore.d.ts.map +0 -1
- package/dist/src/hooks.d.ts.map +0 -1
- package/dist/src/index.d.ts +0 -32
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/reducer.d.ts +0 -49
- package/dist/src/reducer.d.ts.map +0 -1
- package/dist/src/roundHalfToEven.d.ts +0 -10
- package/dist/src/roundHalfToEven.d.ts.map +0 -1
- package/dist/src/types.d.ts +0 -481
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/utils/simple-logger.d.ts +0 -111
- package/dist/src/utils/simple-logger.d.ts.map +0 -1
- package/dist/src/utils.d.ts +0 -164
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/tests/support/dom-harness.d.ts +0 -89
- package/dist/tests/support/dom-harness.d.ts.map +0 -1
- package/src/GlobalDebugOverlay.tsx +0 -284
- package/src/Panel.tsx +0 -297
- package/src/PanelDebugInfo.tsx +0 -94
- package/src/PanelGroup.tsx +0 -245
- package/src/PanelResizeHandle.tsx +0 -758
- package/src/allocateLayout.ts +0 -412
- package/src/context.ts +0 -25
- package/src/debugOverlayStore.ts +0 -355
- package/src/hooks.ts +0 -376
- package/src/index.ts +0 -93
- package/src/reducer.ts +0 -472
- package/src/roundHalfToEven.ts +0 -40
- package/src/styles/components.entry.css +0 -10
- package/src/styles/resize-panels.css +0 -1
- package/src/styles/standalone.entry.css +0 -12
- package/src/types.ts +0 -505
- package/src/utils/simple-logger.ts +0 -186
- package/src/utils.ts +0 -320
package/src/reducer.ts
DELETED
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Panel group reducer: the only writer of panel sizes.
|
|
3
|
-
* パネルグループのリデューサー: パネルサイズを書き込む唯一の主体。
|
|
4
|
-
*
|
|
5
|
-
* Every action edits configuration or preferences and then runs the same allocator, so the first paint, a
|
|
6
|
-
* container resize, a drag, a collapse and a restored layout all produce sizes from one function of
|
|
7
|
-
* (preferences, constraints, container length).
|
|
8
|
-
* すべてのアクションは構成か希望値を編集したうえで同一の配分器を実行する。したがって初回描画・コンテナリサイズ・
|
|
9
|
-
* ドラッグ・折りたたみ・レイアウト復元のいずれも、(希望値, 制約, コンテナ長) の同一関数からサイズを得る。
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { allocatePanelSizes, isPixelPanel, resolvePanelBounds } from "./allocateLayout"
|
|
13
|
-
import { type LayoutConstraintViolation, PANEL_ALLOCATION_EPSILON, type PanelAction, type PanelCollapseDirection, type PanelGroupLayoutState, type PanelLayoutData, type PanelRegistration } from "./types"
|
|
14
|
-
import { calculateSnapThreshold, clamp } from "./utils"
|
|
15
|
-
import { Logger, LogLevel } from "./utils/simple-logger"
|
|
16
|
-
|
|
17
|
-
const logger = new Logger(LogLevel.INFO, "[resize-panels]")
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Empty layout state used before the group has measured itself or registered any panel.
|
|
21
|
-
* グループが自身を計測する前・パネル登録前の空レイアウト状態。
|
|
22
|
-
*/
|
|
23
|
-
export const initialPanelGroupLayoutState: PanelGroupLayoutState = {
|
|
24
|
-
containerSize: 0,
|
|
25
|
-
panels: [],
|
|
26
|
-
violation: null,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Report whether two violations describe the same situation.
|
|
31
|
-
* 2 つの違反が同じ状況を表すかどうかの判定。
|
|
32
|
-
*/
|
|
33
|
-
const isSameViolation = (left: LayoutConstraintViolation | null, right: LayoutConstraintViolation | null): boolean => {
|
|
34
|
-
if (left === null || right === null) {
|
|
35
|
-
return left === right
|
|
36
|
-
}
|
|
37
|
-
return (
|
|
38
|
-
left.reason === right.reason &&
|
|
39
|
-
Math.abs(left.totalMinimumSize - right.totalMinimumSize) <= PANEL_ALLOCATION_EPSILON &&
|
|
40
|
-
Math.abs(left.totalMaximumSize - right.totalMaximumSize) <= PANEL_ALLOCATION_EPSILON &&
|
|
41
|
-
Math.abs(left.availableContainerSize - right.availableContainerSize) <= PANEL_ALLOCATION_EPSILON
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Run the allocator over the given panels and fold the result into a layout state.
|
|
47
|
-
* 与えられたパネルへ配分器を実行し、結果をレイアウト状態へ畳み込む処理。
|
|
48
|
-
*
|
|
49
|
-
* Panel objects and the panel array keep their identity when the allocation did not move them, so a transition
|
|
50
|
-
* that changes nothing produces no new state and no render at all. (When something does change, every panel of
|
|
51
|
-
* the group re-renders: they all read the same context value.)
|
|
52
|
-
* 配分でサイズが動かなかったパネルとパネル配列は同一性を保つため、何も変えない遷移は新しい状態を作らず再描画も
|
|
53
|
-
* 起こらない (何かが変われば、同じコンテキスト値を読むグループ内の全パネルが再描画される)。
|
|
54
|
-
*
|
|
55
|
-
* @param panels - Panels in DOM order with up-to-date preferences / 希望値が最新の DOM 順パネル一覧
|
|
56
|
-
* @param containerSize - Container content-box length in pixels / コンテナ内容領域長 (px)
|
|
57
|
-
* @returns Layout state whose sizes satisfy the allocator / 配分結果を反映したレイアウト状態
|
|
58
|
-
*/
|
|
59
|
-
const allocate = (panels: PanelLayoutData[], containerSize: number): PanelGroupLayoutState => {
|
|
60
|
-
const { sizes, violation } = allocatePanelSizes(panels, containerSize)
|
|
61
|
-
|
|
62
|
-
let changed = false
|
|
63
|
-
const nextPanels = panels.map((panel, index) => {
|
|
64
|
-
const size = panel.collapsed ? 0 : sizes[index]
|
|
65
|
-
if (Math.abs(panel.size - size) <= PANEL_ALLOCATION_EPSILON) {
|
|
66
|
-
return panel
|
|
67
|
-
}
|
|
68
|
-
changed = true
|
|
69
|
-
return { ...panel, size }
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
const next = { containerSize, panels: changed ? nextPanels : panels, violation }
|
|
73
|
-
reportInvariantBreach(next)
|
|
74
|
-
return next
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Find the layout invariant breach in a state, if there is one.
|
|
79
|
-
* レイアウト状態に不変条件の違反があれば、その内容を返す処理。
|
|
80
|
-
*
|
|
81
|
-
* The condition is the one the whole package rests on: with a measured container and no reported constraint
|
|
82
|
-
* conflict, the active panels fill it exactly. The misaligned handle this package once shipped was a silent
|
|
83
|
-
* violation of exactly this — the sizes stopped filling the container and nothing noticed.
|
|
84
|
-
* ここで見る条件はパッケージ全体の土台である: コンテナが計測済みで制約矛盾も報告されていないなら、
|
|
85
|
-
* アクティブなパネルはコンテナをちょうど満たす。かつて出荷された「ハンドルがずれる」欠陥は、まさにこの条件が
|
|
86
|
-
* 無言で破れた状態だった。
|
|
87
|
-
*
|
|
88
|
-
* @param state - Layout state to check / 検査するレイアウト状態
|
|
89
|
-
* @returns Allocated total and container length when they disagree, otherwise null / 食い違う場合の合計と
|
|
90
|
-
* コンテナ長、問題なければ null
|
|
91
|
-
*/
|
|
92
|
-
export const findLayoutInvariantBreach = (state: PanelGroupLayoutState): { allocatedTotal: number; containerSize: number } | null => {
|
|
93
|
-
if (state.containerSize <= 0 || state.violation !== null) {
|
|
94
|
-
return null
|
|
95
|
-
}
|
|
96
|
-
let total = 0
|
|
97
|
-
let activeCount = 0
|
|
98
|
-
for (const panel of state.panels) {
|
|
99
|
-
if (!panel.collapsed) {
|
|
100
|
-
total += panel.size
|
|
101
|
-
activeCount += 1
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// 全パネルが折りたたまれたグループは「満たすべきパネルが無い」正当な状態
|
|
105
|
-
if (activeCount === 0) {
|
|
106
|
-
return null
|
|
107
|
-
}
|
|
108
|
-
const tolerance = PANEL_ALLOCATION_EPSILON * Math.max(1, state.containerSize)
|
|
109
|
-
if (!Number.isFinite(total) || Math.abs(total - state.containerSize) > tolerance) {
|
|
110
|
-
return { allocatedTotal: total, containerSize: state.containerSize }
|
|
111
|
-
}
|
|
112
|
-
return null
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Report a layout invariant breach, which can only come from a defect in this package.
|
|
117
|
-
* レイアウト不変条件の違反を報告する処理 (違反はこのパッケージの欠陥でしか起こり得ない)。
|
|
118
|
-
*
|
|
119
|
-
* The check is O(panels) with no allocation, so it runs on every transition rather than behind a flag.
|
|
120
|
-
* 検査はパネル数に比例するだけで確保も伴わないため、フラグに隠さずすべての遷移で実行する。
|
|
121
|
-
*
|
|
122
|
-
* @param state - Layout state produced by the allocator / 配分器が生成したレイアウト状態
|
|
123
|
-
*/
|
|
124
|
-
const reportInvariantBreach = (state: PanelGroupLayoutState): void => {
|
|
125
|
-
const breach = findLayoutInvariantBreach(state)
|
|
126
|
-
if (breach) {
|
|
127
|
-
logger.error("Allocated sizes do not fill the container.", {
|
|
128
|
-
...breach,
|
|
129
|
-
panels: state.panels.map((panel) => ({ id: panel.id, size: panel.size, collapsed: panel.collapsed })),
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Fold a freshly allocated state into the previous one, preserving identity where nothing moved.
|
|
136
|
-
* 新たに配分した状態を直前の状態へ畳み込み、変化が無い部分の同一性を保つ処理。
|
|
137
|
-
*/
|
|
138
|
-
const commit = (previous: PanelGroupLayoutState, next: PanelGroupLayoutState): PanelGroupLayoutState => {
|
|
139
|
-
if (previous.panels === next.panels && previous.containerSize === next.containerSize && isSameViolation(previous.violation, next.violation)) {
|
|
140
|
-
return previous
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
containerSize: next.containerSize,
|
|
144
|
-
panels: next.panels,
|
|
145
|
-
violation: isSameViolation(previous.violation, next.violation) ? previous.violation : next.violation,
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Create the layout data of a newly registered panel.
|
|
151
|
-
* 新規登録パネルのレイアウトデータを生成する処理。
|
|
152
|
-
*/
|
|
153
|
-
const createPanel = (registration: PanelRegistration, defaultCollapsedFrom: PanelCollapseDirection | null): PanelLayoutData => {
|
|
154
|
-
const preference = registration.sizeUnit === "pixels" ? registration.preferredPixelSize : registration.preferredPercentageSize
|
|
155
|
-
return {
|
|
156
|
-
...registration,
|
|
157
|
-
size: 0,
|
|
158
|
-
collapsed: registration.collapsed,
|
|
159
|
-
collapsedByDirection: registration.collapsed ? defaultCollapsedFrom : null,
|
|
160
|
-
preferenceBeforeCollapse: preference,
|
|
161
|
-
authoredPreference: preference,
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Resolve the direction a panel collapses to when it starts collapsed.
|
|
167
|
-
* 初期状態で折りたたまれているパネルの折りたたみ方向を解決する処理。
|
|
168
|
-
*/
|
|
169
|
-
const initialCollapseDirection = (registration: Pick<PanelRegistration, "collapseFromStart" | "collapseFromEnd">): PanelCollapseDirection | null => {
|
|
170
|
-
if (registration.collapseFromStart) {
|
|
171
|
-
return "start"
|
|
172
|
-
}
|
|
173
|
-
if (registration.collapseFromEnd) {
|
|
174
|
-
return "end"
|
|
175
|
-
}
|
|
176
|
-
return null
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Rewrite every active panel's preference so that it reproduces the sizes it currently has.
|
|
181
|
-
* アクティブな各パネルの希望値を、現在のサイズを再現する値へ書き換える処理。
|
|
182
|
-
*
|
|
183
|
-
* A drag is a statement about proportions, so the sizes it produced become the new preferences. Because a
|
|
184
|
-
* flexible panel's weight is then exactly `size / C × 100`, re-running the allocator returns the very same
|
|
185
|
-
* sizes: the layout the user released the pointer on is a fixed point.
|
|
186
|
-
* ドラッグは比率に対する利用者の意思表示なので、その結果のサイズを新たな希望値とする。柔軟パネルの重みは
|
|
187
|
-
* `size / C × 100` そのものになるため、配分器を再実行しても同じサイズが返る (ドラッグ結果は不動点)。
|
|
188
|
-
*/
|
|
189
|
-
const commitPreferences = (panels: PanelLayoutData[], containerSize: number): PanelLayoutData[] => {
|
|
190
|
-
if (containerSize <= 0) {
|
|
191
|
-
return panels
|
|
192
|
-
}
|
|
193
|
-
// 端まで引いて折りたたむ操作では、閉じる直前のフレームの寸法が復元用に残ると
|
|
194
|
-
// 展開しても潰れたままになる。復元用の希望値は「閉じたとみなす幅より広い間」だけ更新する
|
|
195
|
-
const snapThreshold = calculateSnapThreshold(containerSize)
|
|
196
|
-
return panels.map((panel) => {
|
|
197
|
-
if (panel.collapsed) {
|
|
198
|
-
return panel
|
|
199
|
-
}
|
|
200
|
-
const preference = isPixelPanel(panel) ? panel.size : (panel.size / containerSize) * 100
|
|
201
|
-
const current = isPixelPanel(panel) ? panel.preferredPixelSize : panel.preferredPercentageSize
|
|
202
|
-
const preferenceBeforeCollapse = panel.size > snapThreshold ? preference : panel.preferenceBeforeCollapse
|
|
203
|
-
if (current === preference && preferenceBeforeCollapse === panel.preferenceBeforeCollapse) {
|
|
204
|
-
return panel
|
|
205
|
-
}
|
|
206
|
-
return isPixelPanel(panel) ? { ...panel, preferredPixelSize: preference, preferenceBeforeCollapse } : { ...panel, preferredPercentageSize: preference, preferenceBeforeCollapse }
|
|
207
|
-
})
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Set a panel's preference from an absolute pixel length.
|
|
212
|
-
* 絶対ピクセル長からパネルの希望値を設定する処理。
|
|
213
|
-
*
|
|
214
|
-
* A flexible panel's weight is a share of the container, so an absolute length only lands where it was
|
|
215
|
-
* asked for when the active panels' weights are expressed on the same scale — which is why every caller
|
|
216
|
-
* that hands out absolute lengths rewrites the whole set.
|
|
217
|
-
* 柔軟パネルの重みはコンテナ比なので、絶対長がそのままの位置に収まるのはアクティブパネルの重みが同じ尺度で
|
|
218
|
-
* 表されているときだけである。絶対長を配る呼び出し側が常に集合全体を書き換えるのはこのため。
|
|
219
|
-
*/
|
|
220
|
-
const withPreferenceFromPixels = (panel: PanelLayoutData, pixels: number, containerSize: number): PanelLayoutData => {
|
|
221
|
-
if (isPixelPanel(panel)) {
|
|
222
|
-
const preferredPixelSize = Math.max(0, pixels)
|
|
223
|
-
return { ...panel, preferredPixelSize, preferenceBeforeCollapse: preferredPixelSize }
|
|
224
|
-
}
|
|
225
|
-
if (containerSize <= 0) {
|
|
226
|
-
return panel
|
|
227
|
-
}
|
|
228
|
-
const preferredPercentageSize = Math.max(0, (pixels / containerSize) * 100)
|
|
229
|
-
return { ...panel, preferredPercentageSize, preferenceBeforeCollapse: preferredPercentageSize }
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Express a panel's preference as a length in pixels for the given container.
|
|
234
|
-
* パネルの希望値を、指定コンテナに対するピクセル長として表す処理。
|
|
235
|
-
*
|
|
236
|
-
* This is the length the panel occupies when it is open, which is what a persisted layout has to carry: the
|
|
237
|
-
* allocated size of a collapsed panel is 0 and would restore as "closed and with nothing to open to".
|
|
238
|
-
* これは「開いているときに占める長さ」であり、保存レイアウトが持つべき値でもある。折りたたみ中のパネルの
|
|
239
|
-
* 配分結果は 0 であり、そのまま保存すると「閉じたまま、開く先も無い」状態として復元されてしまう。
|
|
240
|
-
*
|
|
241
|
-
* @param panel - Panel layout data / パネルレイアウトデータ
|
|
242
|
-
* @param containerSize - Container content-box length in pixels / コンテナ内容領域長 (px)
|
|
243
|
-
* @returns Preferred length in pixels / 希望する長さ (px)
|
|
244
|
-
*/
|
|
245
|
-
export const getPanelPreferenceInPixels = (panel: PanelLayoutData, containerSize: number): number => {
|
|
246
|
-
const preference = panel.collapsed ? panel.preferenceBeforeCollapse : isPixelPanel(panel) ? panel.preferredPixelSize : panel.preferredPercentageSize
|
|
247
|
-
return isPixelPanel(panel) ? preference : (preference / 100) * containerSize
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Give one panel an absolute length and let the other active panels share what is left, keeping their ratio.
|
|
252
|
-
* 1 枚のパネルへ絶対長を与え、残りのアクティブパネルが現在の比のまま残余を分け合うよう希望値を揃える処理。
|
|
253
|
-
*
|
|
254
|
-
* @param panels - Panels in DOM order / DOM 順のパネル一覧
|
|
255
|
-
* @param targetId - Panel that receives the absolute length / 絶対長を受け取るパネル
|
|
256
|
-
* @param pixels - Absolute length in pixels / 絶対長 (px)
|
|
257
|
-
* @param containerSize - Container content-box length in pixels / コンテナ内容領域長 (px)
|
|
258
|
-
* @returns Panels whose preferences reproduce the requested layout / 要求どおりの配置を再現する希望値を持つパネル一覧
|
|
259
|
-
*/
|
|
260
|
-
const withAbsoluteSize = (panels: PanelLayoutData[], targetId: string, pixels: number, containerSize: number): PanelLayoutData[] => {
|
|
261
|
-
const target = Math.max(0, Math.min(pixels, containerSize))
|
|
262
|
-
// ピクセルパネルの希望長は利用者が宣言した絶対値なので、他のパネルの都合で書き換えない。
|
|
263
|
-
// 残余を分け合うのは柔軟パネルだけ
|
|
264
|
-
const sharing = panels.filter((panel) => panel.id !== targetId && !panel.collapsed && !isPixelPanel(panel))
|
|
265
|
-
const reserved = panels.reduce((total, panel) => (panel.id !== targetId && !panel.collapsed && isPixelPanel(panel) ? total + panel.size : total), 0)
|
|
266
|
-
const sharingTotal = sharing.reduce((total, panel) => total + panel.size, 0)
|
|
267
|
-
const sharingBudget = Math.max(0, containerSize - target - reserved)
|
|
268
|
-
|
|
269
|
-
return panels.map((panel) => {
|
|
270
|
-
if (panel.id === targetId) {
|
|
271
|
-
return withPreferenceFromPixels(panel, target, containerSize)
|
|
272
|
-
}
|
|
273
|
-
if (panel.collapsed || isPixelPanel(panel) || sharing.length === 0) {
|
|
274
|
-
return panel
|
|
275
|
-
}
|
|
276
|
-
const share = sharingTotal > 0 ? (panel.size / sharingTotal) * sharingBudget : sharingBudget / sharing.length
|
|
277
|
-
return withPreferenceFromPixels(panel, share, containerSize)
|
|
278
|
-
})
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Reduce the layout state of one panel group.
|
|
283
|
-
* パネルグループのレイアウト状態を遷移させるリデューサー。
|
|
284
|
-
*
|
|
285
|
-
* @param state - Current layout state / 現在のレイアウト状態
|
|
286
|
-
* @param action - Action describing the edit / 編集内容を表すアクション
|
|
287
|
-
* @returns Next layout state, identical by reference when nothing changed / 次のレイアウト状態 (無変化なら同一参照)
|
|
288
|
-
*/
|
|
289
|
-
export const panelReducer = (state: PanelGroupLayoutState, action: PanelAction): PanelGroupLayoutState => {
|
|
290
|
-
switch (action.type) {
|
|
291
|
-
case "SET_CONTAINER_SIZE": {
|
|
292
|
-
if (!Number.isFinite(action.containerSize) || action.containerSize < 0 || action.containerSize === state.containerSize) {
|
|
293
|
-
return state
|
|
294
|
-
}
|
|
295
|
-
return commit(state, allocate(state.panels, action.containerSize))
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
case "REGISTER_PANEL": {
|
|
299
|
-
const index = state.panels.findIndex((panel) => panel.id === action.panel.id)
|
|
300
|
-
if (index === -1) {
|
|
301
|
-
const panels = [...state.panels, createPanel(action.panel, initialCollapseDirection(action.panel))]
|
|
302
|
-
return commit(state, allocate(panels, state.containerSize))
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const previous = state.panels[index]
|
|
306
|
-
const authored = action.panel.sizeUnit === "pixels" ? action.panel.preferredPixelSize : action.panel.preferredPercentageSize
|
|
307
|
-
// 利用者が指定した既定サイズが変わったときだけ希望値を採用する。
|
|
308
|
-
// 変わっていない再登録 (制約プロパティの変更など) でドラッグ結果を巻き戻さないための判定
|
|
309
|
-
const adoptsAuthoredPreference = authored !== previous.authoredPreference || action.panel.sizeUnit !== previous.sizeUnit
|
|
310
|
-
const collapseDirectionStillAllowed = previous.collapsedByDirection === "start" ? action.panel.collapseFromStart : previous.collapsedByDirection === "end" ? action.panel.collapseFromEnd : false
|
|
311
|
-
// 折りたたみ設定が変わったのに方向が残ると、どの方向でも展開できないパネルになる
|
|
312
|
-
const stillCollapsible = action.panel.collapseFromStart || action.panel.collapseFromEnd
|
|
313
|
-
const nextCollapsed = previous.collapsed && stillCollapsible
|
|
314
|
-
const updated: PanelLayoutData = {
|
|
315
|
-
...previous,
|
|
316
|
-
...action.panel,
|
|
317
|
-
collapsed: nextCollapsed,
|
|
318
|
-
collapsedByDirection: nextCollapsed ? (collapseDirectionStillAllowed ? previous.collapsedByDirection : initialCollapseDirection(action.panel)) : null,
|
|
319
|
-
preferredPercentageSize: adoptsAuthoredPreference ? action.panel.preferredPercentageSize : previous.preferredPercentageSize,
|
|
320
|
-
preferredPixelSize: adoptsAuthoredPreference ? action.panel.preferredPixelSize : previous.preferredPixelSize,
|
|
321
|
-
preferenceBeforeCollapse: adoptsAuthoredPreference ? authored : previous.preferenceBeforeCollapse,
|
|
322
|
-
authoredPreference: authored,
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const panels = [...state.panels]
|
|
326
|
-
panels[index] = updated
|
|
327
|
-
return commit(state, allocate(panels, state.containerSize))
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
case "UNREGISTER_PANEL": {
|
|
331
|
-
const panels = state.panels.filter((panel) => panel.id !== action.id)
|
|
332
|
-
if (panels.length === state.panels.length) {
|
|
333
|
-
return state
|
|
334
|
-
}
|
|
335
|
-
return commit(state, allocate(panels, state.containerSize))
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
case "REORDER_PANELS": {
|
|
339
|
-
if (action.ids.length !== state.panels.length) {
|
|
340
|
-
// DOM と登録状態が一時的にずれている間は並べ替えない (揃った時点で再度通知される)
|
|
341
|
-
return state
|
|
342
|
-
}
|
|
343
|
-
if (new Set(action.ids).size !== action.ids.length) {
|
|
344
|
-
// 同じ id が 2 度現れる並びは、片方のパネルを取り違えて落とすことになる
|
|
345
|
-
return state
|
|
346
|
-
}
|
|
347
|
-
const byId = new Map(state.panels.map((panel) => [panel.id, panel]))
|
|
348
|
-
const panels: PanelLayoutData[] = []
|
|
349
|
-
for (const id of action.ids) {
|
|
350
|
-
const panel = byId.get(id)
|
|
351
|
-
if (!panel) {
|
|
352
|
-
return state
|
|
353
|
-
}
|
|
354
|
-
panels.push(panel)
|
|
355
|
-
}
|
|
356
|
-
if (panels.every((panel, index) => panel === state.panels[index])) {
|
|
357
|
-
return state
|
|
358
|
-
}
|
|
359
|
-
return commit(state, allocate(panels, state.containerSize))
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
case "RESIZE_PANELS": {
|
|
363
|
-
const startIndex = state.panels.findIndex((panel) => panel.id === action.startId)
|
|
364
|
-
const endIndex = state.panels.findIndex((panel) => panel.id === action.endId)
|
|
365
|
-
if (startIndex === -1 || endIndex === -1) {
|
|
366
|
-
return state
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const startPanel = state.panels[startIndex]
|
|
370
|
-
const endPanel = state.panels[endIndex]
|
|
371
|
-
if (startPanel.collapsed || endPanel.collapsed || state.containerSize <= 0 || !Number.isFinite(action.startSize)) {
|
|
372
|
-
return state
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// ドラッグはこの 2 枚の間で領域を移すだけの操作なので、合計は動かさない
|
|
376
|
-
const total = startPanel.size + endPanel.size
|
|
377
|
-
const startBounds = resolvePanelBounds(startPanel, state.containerSize)
|
|
378
|
-
const endBounds = resolvePanelBounds(endPanel, state.containerSize)
|
|
379
|
-
const lowerBound = Math.max(startBounds.min, total - endBounds.max)
|
|
380
|
-
const upperBound = Math.min(startBounds.max, total - endBounds.min)
|
|
381
|
-
if (lowerBound - upperBound > PANEL_ALLOCATION_EPSILON) {
|
|
382
|
-
return state
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const startSize = clamp(action.startSize, lowerBound, Math.max(lowerBound, upperBound))
|
|
386
|
-
const endSize = total - startSize
|
|
387
|
-
if (Math.abs(startSize - startPanel.size) <= PANEL_ALLOCATION_EPSILON) {
|
|
388
|
-
return state
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const resized = [...state.panels]
|
|
392
|
-
resized[startIndex] = { ...startPanel, size: startSize }
|
|
393
|
-
resized[endIndex] = { ...endPanel, size: endSize }
|
|
394
|
-
return commit(state, allocate(commitPreferences(resized, state.containerSize), state.containerSize))
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
case "COLLAPSE_PANEL": {
|
|
398
|
-
const index = state.panels.findIndex((panel) => panel.id === action.id)
|
|
399
|
-
if (index === -1) {
|
|
400
|
-
return state
|
|
401
|
-
}
|
|
402
|
-
const panel = state.panels[index]
|
|
403
|
-
const allowed = action.from === "start" ? panel.collapseFromStart : panel.collapseFromEnd
|
|
404
|
-
if (!allowed || panel.collapsed) {
|
|
405
|
-
return state
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const panels = [...state.panels]
|
|
409
|
-
panels[index] = {
|
|
410
|
-
...panel,
|
|
411
|
-
collapsed: true,
|
|
412
|
-
collapsedByDirection: action.from,
|
|
413
|
-
}
|
|
414
|
-
return commit(state, allocate(panels, state.containerSize))
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
case "EXPAND_PANEL": {
|
|
418
|
-
const index = state.panels.findIndex((panel) => panel.id === action.id)
|
|
419
|
-
if (index === -1 || (action.targetSize !== undefined && state.containerSize <= 0)) {
|
|
420
|
-
return state
|
|
421
|
-
}
|
|
422
|
-
const panel = state.panels[index]
|
|
423
|
-
const allowed = action.from === "start" ? panel.collapseFromStart : panel.collapseFromEnd
|
|
424
|
-
if (!(allowed && panel.collapsed) || (panel.collapsedByDirection !== null && panel.collapsedByDirection !== action.from)) {
|
|
425
|
-
return state
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const restored: PanelLayoutData = {
|
|
429
|
-
...panel,
|
|
430
|
-
collapsed: false,
|
|
431
|
-
collapsedByDirection: null,
|
|
432
|
-
preferredPixelSize: isPixelPanel(panel) ? panel.preferenceBeforeCollapse : panel.preferredPixelSize,
|
|
433
|
-
preferredPercentageSize: isPixelPanel(panel) ? panel.preferredPercentageSize : panel.preferenceBeforeCollapse,
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const expanded = [...state.panels]
|
|
437
|
-
expanded[index] = restored
|
|
438
|
-
const panels = action.targetSize === undefined ? expanded : withAbsoluteSize(expanded, action.id, action.targetSize, state.containerSize)
|
|
439
|
-
return commit(state, allocate(panels, state.containerSize))
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
case "RESTORE_LAYOUT": {
|
|
443
|
-
if (action.entries.length !== state.panels.length || state.containerSize <= 0) {
|
|
444
|
-
return state
|
|
445
|
-
}
|
|
446
|
-
const stored = new Map(action.entries.map((entry) => [entry.id, entry]))
|
|
447
|
-
if (state.panels.some((panel) => !stored.has(panel.id))) {
|
|
448
|
-
// 保存時と構成が違う。位置で当てはめると別のパネルへ他人の寸法を与えてしまうため適用しない
|
|
449
|
-
return state
|
|
450
|
-
}
|
|
451
|
-
const panels = state.panels.map((panel) => {
|
|
452
|
-
const entry = stored.get(panel.id)
|
|
453
|
-
if (!entry) {
|
|
454
|
-
return panel
|
|
455
|
-
}
|
|
456
|
-
// 保存されているのは「開いているときに占める長さ」なので、折りたたみ状態に関わらず希望値として適用する
|
|
457
|
-
const restored = withPreferenceFromPixels(panel, entry.size, state.containerSize)
|
|
458
|
-
const canCollapse = panel.collapseFromStart || panel.collapseFromEnd
|
|
459
|
-
if (entry.collapsed && canCollapse) {
|
|
460
|
-
return { ...restored, collapsed: true, collapsedByDirection: panel.collapsedByDirection ?? initialCollapseDirection(panel) }
|
|
461
|
-
}
|
|
462
|
-
return restored.collapsed ? { ...restored, collapsed: false, collapsedByDirection: null } : restored
|
|
463
|
-
})
|
|
464
|
-
logger.debug("restore layout", { containerSize: state.containerSize, entries: action.entries })
|
|
465
|
-
return commit(state, allocate(panels, state.containerSize))
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
default: {
|
|
469
|
-
return state
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
package/src/roundHalfToEven.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Round a number to specified digits using banker's rounding (round half to even).
|
|
3
|
-
* 指定した桁で銀行丸め (最近接偶数への丸め) を行う関数。
|
|
4
|
-
*
|
|
5
|
-
* @param value - 丸め対象の数値
|
|
6
|
-
* @param digits - 丸める桁数 (0: 整数, 1: 小数第1位, -1: 十の位)
|
|
7
|
-
* @returns 丸められた数値
|
|
8
|
-
*/
|
|
9
|
-
export const roundHalfToEven = (value: number, digits: number = 0): number => {
|
|
10
|
-
// 非有限値はそのまま返す
|
|
11
|
-
if (!Number.isFinite(value)) {
|
|
12
|
-
return value
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// 桁を移動させるための乗数を計算
|
|
16
|
-
const multiplier = 10 ** digits
|
|
17
|
-
const shifted = value * multiplier
|
|
18
|
-
|
|
19
|
-
// 整数部分と小数部分を分離
|
|
20
|
-
const floor = Math.floor(shifted)
|
|
21
|
-
const fraction = shifted - floor
|
|
22
|
-
|
|
23
|
-
// 浮動小数点誤差を考慮したイプシロン値
|
|
24
|
-
const epsilon = Number.EPSILON * Math.max(1, Math.abs(shifted))
|
|
25
|
-
|
|
26
|
-
// 0.5 未満の場合は切り捨て
|
|
27
|
-
if (fraction < 0.5 - epsilon) {
|
|
28
|
-
return floor / multiplier
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// 0.5 より大きい場合は切り上げ
|
|
32
|
-
if (fraction > 0.5 + epsilon) {
|
|
33
|
-
return (floor + 1) / multiplier
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ちょうど 0.5 の場合は、最近接偶数に丸める
|
|
37
|
-
// floor が偶数なら floor、奇数なら floor + 1
|
|
38
|
-
const rounded = floor % 2 === 0 ? floor : floor + 1
|
|
39
|
-
return rounded / multiplier
|
|
40
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Artifact A: components-only CSS (Tailwind ホスト向け).
|
|
3
|
-
* 手書きコンポーネントクラスのみ (resize-panels は大半のスタイルを JSX ユーティリティで持つため
|
|
4
|
-
* この成果物はほぼ空になる)。preflight・:root テーマ変数・ユーティリティは含まない
|
|
5
|
-
* (preflight を Tailwind ホストへ流し込むとホストの base 層を破壊するため厳禁)。
|
|
6
|
-
*/
|
|
7
|
-
@import "tailwindcss/theme.css" theme(reference);
|
|
8
|
-
@layer components {
|
|
9
|
-
@import "./resize-panels.css";
|
|
10
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/* nothing */
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/*! @aiquants/resize-panels standalone CSS — 非 Tailwind ホスト専用。ホストの Tailwind ビルドと混在させないこと */
|
|
2
|
-
/*
|
|
3
|
-
* Artifact B: 自己完結スタンドアロン. 非 Tailwind ホストの利便のため preflight を同梱する
|
|
4
|
-
* (Tailwind ホストはホスト自身の preflight があるので Artifact A を使うこと)。
|
|
5
|
-
*/
|
|
6
|
-
@layer theme, base, components, utilities;
|
|
7
|
-
@import "tailwindcss/theme.css" layer(theme);
|
|
8
|
-
@import "tailwindcss/preflight.css" layer(base);
|
|
9
|
-
@import "tailwindcss/utilities.css" layer(utilities) source(none);
|
|
10
|
-
@import "./resize-panels.css" layer(components);
|
|
11
|
-
@source "../**/*.{ts,tsx}";
|
|
12
|
-
@custom-variant dark (&:where(.dark, .dark *));
|