@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
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
- }
@@ -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 *));