@aiquants/spreadsheet 0.8.0 → 0.10.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/CHANGELOG.md +104 -0
- package/README.md +160 -13
- package/dist/index.cjs +13 -13
- package/dist/index.d.cts +156 -7
- package/dist/index.d.ts +156 -7
- package/dist/index.js +4508 -4057
- package/dist/styles/spreadsheet.css +1 -1
- package/dist/styles/spreadsheet.standalone.css +1 -1
- package/package.json +5 -5
- package/src/Spreadsheet.tsx +350 -34
- package/src/clipboard/tiling.ts +106 -16
- package/src/clipboard/useClipboard.ts +120 -7
- package/src/commands/useCommands.ts +226 -42
- package/src/editor/useCellEditor.ts +62 -13
- package/src/grid/GridBody.tsx +45 -3
- package/src/header/ColumnHeader.tsx +84 -12
- package/src/header/RowHeader.tsx +79 -11
- package/src/header/resizeStrips.ts +86 -1
- package/src/header/useHeaderResize.ts +88 -20
- package/src/header/useHeaderSelection.ts +28 -9
- package/src/index.ts +5 -1
- package/src/keyboard/ensureCellVisible.ts +40 -17
- package/src/keyboard/useGridKeyboard.ts +34 -7
- package/src/model/dataAdapter.ts +121 -2
- package/src/model/frozenPartition.ts +183 -59
- package/src/model/labels.ts +8 -2
- package/src/overlay/SelectionOverlay.tsx +599 -116
- package/src/overlay/useFillHandle.ts +100 -13
- package/src/popover/CommentPopover.tsx +14 -2
- package/src/popover/LinkPopover.tsx +15 -3
- package/src/popover/NotePopover.tsx +24 -6
- package/src/popover/usePopovers.ts +128 -18
- package/src/selection/useAutoScroller.ts +58 -15
- package/src/selection/useSelectionEngine.ts +27 -10
- package/src/styles/spreadsheet.css +32 -0
|
@@ -44,6 +44,46 @@
|
|
|
44
44
|
* - ポップオーバーアンカー: ポップオーバー層所有の 2 ノードを fill ハンドルと同じ凍結分割写像でセルへ
|
|
45
45
|
* 配置 (凍結帯の下 / viewport 外は非表示)。
|
|
46
46
|
* - ドラッグ矩形モード "point" (参照ドラッグ) は `data-aqsp-drag-mode="point"` を描く。
|
|
47
|
+
*
|
|
48
|
+
* v0.9 trailing freeze + freeze divider (plan §2.4 / §2.5 / §4.10, ADR-22 / ADR-24): each axis
|
|
49
|
+
* splits three ways — `[f?] + [s] + [t?]` (kind `m` still means an UNSPLIT axis, so `mm` keeps
|
|
50
|
+
* meaning "no freeze anywhere") — over the full 16-key {m,f,s,t}² pre-seeded registry with paint
|
|
51
|
+
* rank m 0 < s 1 < t 2 < f 3. Divider segments (`[data-aqsp-freeze-divider]`) are static JSX
|
|
52
|
+
* children of scroll-kind quads placed immediately before the active frame; the runtime stack is
|
|
53
|
+
* bands < fills < dividers < activeFrame < dragRect < marquee < fillPreview < resizeGuide, and
|
|
54
|
+
* BOTH dynamic insertion anchors go through the single `chromeFloorOf` helper (census #24/#25).
|
|
55
|
+
* Segment existence is count-driven (render), visibility is the equality-suppressed `display`
|
|
56
|
+
* heal in `layersFor` (band px), geometry comes from the quad clips — zero per-frame divider
|
|
57
|
+
* writes by construction. Fill-pool writes gain the same-value suppression (census #23): a
|
|
58
|
+
* steady scroll frame writes `transform` only — and `transform` itself is same-value cached on
|
|
59
|
+
* both band segments and fill slots (plan §5.9 step 3 first-stage retreat), so quadrant clones
|
|
60
|
+
* that do not move on a pure cross-axis scroll write nothing at all. Trailing (T = 0 /
|
|
61
|
+
* undeclared) reproduces the v0.8 DOM byte-identically as a capability branch. The clip marquee
|
|
62
|
+
* follows a seam edge-ownership rule (the pane-seam analogue of the cell-border "one of the two
|
|
63
|
+
* neighbours paints the shared line" fold): at a pane seam exactly one quadrant — the one whose
|
|
64
|
+
* half-open clip interval contains the range's adjacent cells — paints the shared edge; a
|
|
65
|
+
* non-owner whose seam-side edge sits in the contested outset strip ([clamp, clamp + 3) /
|
|
66
|
+
* (−3, 0]) hides its marquee clone so the progressive ants outset cannot pull a phantom dash
|
|
67
|
+
* line back across the seam — outside that strip the clip already blanks the clone and the
|
|
68
|
+
* write pattern stays byte-identical to v0.8.
|
|
69
|
+
*
|
|
70
|
+
* v0.9 末尾凍結 + 凍結分割線 (計画 §2.4 / §2.5 / §4.10、ADR-22 / ADR-24): 各軸は `[f?] + [s] +
|
|
71
|
+
* [t?]` の 3 分割 (kind `m` は「未分割軸」の意のままで `mm` は「どこにも凍結なし」を保つ)、
|
|
72
|
+
* レジストリは {m,f,s,t}² = 16 キーの事前シード、描画順位は m 0 < s 1 < t 2 < f 3。分割線
|
|
73
|
+
* セグメント (`[data-aqsp-freeze-divider]`) は scroll 種象限の静的 JSX 子として activeFrame の
|
|
74
|
+
* 直前に置き、実行時スタックは 帯 < fill < 分割線 < activeFrame < dragRect < marquee <
|
|
75
|
+
* fillPreview < resizeGuide。動的挿入アンカー 2 つは単一ヘルパ `chromeFloorOf` を通す (census
|
|
76
|
+
* #24 / #25)。存在 = 実効件数 (render)、可視 = `layersFor` の等値抑制 `display` heal (帯 px)、
|
|
77
|
+
* 幾何 = 象限クリップ任せ — 毎フレームの分割線書込は構造的に 0。fill プールは同値抑制を得て
|
|
78
|
+
* (census #23) 定常スクロールフレームの書込は `transform` のみ — さらに `transform` 自体も帯
|
|
79
|
+
* セグメント / fill スロットの両書込路で同値キャッシュ (計画 §5.9 手順 3 第 1 段リトリート) され、
|
|
80
|
+
* 純横 / 純縦スクロールで動かない象限クローンは何も書かない。末尾 (T = 0 / 未宣言) は能力
|
|
81
|
+
* 分岐として v0.8 の DOM を byte 恒等に再現する。クリップマーキーは継ぎ目の縁所有権規則に従う
|
|
82
|
+
* (セル罫線の「共有線は隣接の一方だけが塗る」畳込みのペイン継ぎ目版): ペイン継ぎ目の共有縁は、
|
|
83
|
+
* レンジの隣接セル列を半開クリップ区間に含む象限ちょうど 1 つが塗り、継ぎ目側の縁が争奪
|
|
84
|
+
* アウトセット帯 ([clamp, clamp + 3) / (−3, 0]) に載った非所有クローンは消灯する — 漸進アンツ
|
|
85
|
+
* アウトセットが幽霊破線を継ぎ目の向こうへ引き戻せないようにするため。帯の外はクリップが元より
|
|
86
|
+
* 不可視化しており、書込パターンは v0.8 と byte 恒等のまま。
|
|
47
87
|
*/
|
|
48
88
|
|
|
49
89
|
import { useWheelBridge, type VirtualGridHandle } from "@aiquants/virtualscroll"
|
|
@@ -52,7 +92,7 @@ import type { AxisModel } from "../model/axisModel.ts"
|
|
|
52
92
|
import { isWholeCols, isWholeRows } from "../model/cellAnnotations.ts"
|
|
53
93
|
import { type CellCoord, type CellRange, makeRange } from "../model/cellCoord.ts"
|
|
54
94
|
import { type EditPaint, IDLE_EDIT_PAINT } from "../model/cellPaint.ts"
|
|
55
|
-
import { colToViewportX, frozenHeightOf, frozenWidthOf, rowToViewportY } from "../model/frozenPartition.ts"
|
|
95
|
+
import { type AxisForwardCtx, colToViewportX, frozenHeightOf, frozenWidthOf, rowToViewportY, trailingHeightOf, trailingWidthOf } from "../model/frozenPartition.ts"
|
|
56
96
|
import { SCROLLBAR_WIDTH } from "../model/viewStore.ts"
|
|
57
97
|
import type { PopoverAnchorState } from "../popover/usePopovers.ts"
|
|
58
98
|
import type { SelectionState } from "../selection/selectionState.ts"
|
|
@@ -60,6 +100,17 @@ import type { SelectionState } from "../selection/selectionState.ts"
|
|
|
60
100
|
/** Viewport-space rect (private — distinct from the logical-space LogicalRect). / ビューポート矩形 (非公開)。 */
|
|
61
101
|
type ViewportRect = { top: number; left: number; width: number; height: number }
|
|
62
102
|
|
|
103
|
+
/** `getFrozenSize()` snapshot shape (engine 3.5.0 — 10 fields incl. the trailing family). / `getFrozenSize()` 快照の形 (エンジン 3.5.0 — 末尾族込みの 10 フィールド)。 */
|
|
104
|
+
type FrozenSnapshot = ReturnType<VirtualGridHandle["getFrozenSize"]>
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Pre-attach frozen-size snapshot (all zeros — the capability branch for a not-yet-attached
|
|
108
|
+
* handle, byte-matching the engine's pre-measure state, not a value fallback).
|
|
109
|
+
* 装着前の凍結寸法快照 (全 0 — 未装着ハンドルの能力分岐。エンジンの未測定状態と byte 一致で、
|
|
110
|
+
* 値フォールバックではない)。
|
|
111
|
+
*/
|
|
112
|
+
const EMPTY_FROZEN_SIZE: FrozenSnapshot = Object.freeze({ cols: 0, width: 0, rows: 0, height: 0, trailingCols: 0, trailingRows: 0, trailingWidth: 0, trailingHeight: 0, trailingVisibleWidth: 0, trailingVisibleHeight: 0 })
|
|
113
|
+
|
|
63
114
|
/** Clamp margin: one screen each side. / クランプ余白 (± 1 画面)。 */
|
|
64
115
|
const CLAMP_MARGIN_SCREENS = 1
|
|
65
116
|
|
|
@@ -87,6 +138,22 @@ export const DEFAULT_HIGHLIGHT_COLORS: HighlightColors = Object.freeze({ row: "#
|
|
|
87
138
|
/** Range cap of the band pool: beyond it only the TAIL ranges carry bands (deviation row). / 帯プールのレンジ上限 (超過は末尾レンジだけ — 逸脱行)。 */
|
|
88
139
|
export const BAND_RANGE_CAP = 16
|
|
89
140
|
|
|
141
|
+
/** Band-node ceiling above four quadrants (ADR-22 derate — fired at step-5c certification). / 4 象限超の帯ノード総数天井 (ADR-22 デレート — step 5c 認証で発火)。 */
|
|
142
|
+
const BAND_NODE_CEILING = 450
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Effective band-range cap for one repaint, keyed on the active quadrant count (ADR-22
|
|
146
|
+
* derate, v0.9 plan §5.9 step 3): above four quadrants the cap is
|
|
147
|
+
* floor(450 / (5 × quadCount)) — 15 at six quadrants, 10 at nine — so total band nodes never
|
|
148
|
+
* exceed 450; at four quadrants or fewer the v0.8 BAND_RANGE_CAP applies unchanged (T = 0
|
|
149
|
+
* stays byte-identical to v0.8). Exported for the unit value pin (barrel-private).
|
|
150
|
+
* repaint 1 回の実効帯レンジ上限 (象限数キー — ADR-22 デレート、v0.9 計画 §5.9 手順 3)。
|
|
151
|
+
* 5 象限以上は floor(450 / (5 × 象限数)) (6 象限 15 / 9 象限 10) で帯ノード総数 ≤ 450、
|
|
152
|
+
* 4 象限以下は v0.8 の BAND_RANGE_CAP のまま (T = 0 は v0.8 と byte 恒等)。単体値ピン用に
|
|
153
|
+
* モジュール export (バレル非公開)。
|
|
154
|
+
*/
|
|
155
|
+
export const bandRangeCapFor = (quadCount: number): number => (quadCount > 4 ? Math.floor(BAND_NODE_CEILING / (5 * quadCount)) : BAND_RANGE_CAP)
|
|
156
|
+
|
|
90
157
|
/** Live drag-rect modes: §3.2 add / subtract plus the v0.8 "point" (formula-reference drag). / ドラッグ矩形のモード: §3.2 の add / subtract + v0.8 の "point" (参照ドラッグ)。 */
|
|
91
158
|
export type DragFrameMode = "add" | "subtract" | "point"
|
|
92
159
|
|
|
@@ -98,14 +165,33 @@ type BandEdge = "left" | "right" | "above" | "below"
|
|
|
98
165
|
|
|
99
166
|
/**
|
|
100
167
|
* Last-written numbers of one band group — JS-side same-value suppression so a frame writes a
|
|
101
|
-
* size, a display
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
168
|
+
* size, a display, the cross tint or a segment `transform` only when it actually changes (no
|
|
169
|
+
* DOM reads on the hot path). The five `*Transform` strings extend census #23 to `transform`
|
|
170
|
+
* (v0.9 plan §5.9 step 3 first-stage retreat): a pure cross-axis scroll frame leaves the
|
|
171
|
+
* byte-identical transforms of the non-moving quadrant clones unwritten. `crossTint` is the
|
|
172
|
+
* colour family the cross node currently paints ("intersection" = the CSS default, no inline
|
|
173
|
+
* background).
|
|
174
|
+
* 帯 1 組の直近書込値 — JS 側の同値抑止で、サイズ / display / 交点の色 / セグメント `transform` は
|
|
175
|
+
* 実際に変わるフレームだけ書く (ホットパスで DOM を読まない)。`*Transform` 文字列 5 本は census #23
|
|
176
|
+
* の `transform` への拡張 (v0.9 計画 §5.9 手順 3 第 1 段リトリート) — 純横 / 純縦スクロールで動かない
|
|
177
|
+
* 象限クローンの byte 恒等 transform を再書込しない。`crossTint` は交点ノードが現在塗る色族
|
|
178
|
+
* ("intersection" = CSS 既定、インライン background なし)。
|
|
107
179
|
*/
|
|
108
|
-
type BandWriteState = {
|
|
180
|
+
type BandWriteState = {
|
|
181
|
+
rowShown: boolean
|
|
182
|
+
colShown: boolean
|
|
183
|
+
crossShown: boolean
|
|
184
|
+
crossTint: BandKind
|
|
185
|
+
rowHeight: number
|
|
186
|
+
colWidth: number
|
|
187
|
+
crossWidth: number
|
|
188
|
+
crossHeight: number
|
|
189
|
+
rowLeftTransform: string
|
|
190
|
+
rowRightTransform: string
|
|
191
|
+
colAboveTransform: string
|
|
192
|
+
colBelowTransform: string
|
|
193
|
+
crossTransform: string
|
|
194
|
+
}
|
|
109
195
|
|
|
110
196
|
/** One range's band group: two row segments, two column segments and the intersection node. / レンジ 1 つの帯組: 行 2 セグメント + 列 2 セグメント + 交点ノード。 */
|
|
111
197
|
export type BandNodes = { rowLeft: HTMLDivElement; rowRight: HTMLDivElement; colAbove: HTMLDivElement; colBelow: HTMLDivElement; cross: HTMLDivElement; last: BandWriteState }
|
|
@@ -140,7 +226,7 @@ export const createBandNodes = (): BandNodes => ({
|
|
|
140
226
|
colAbove: makeBandNode("column", "above"),
|
|
141
227
|
colBelow: makeBandNode("column", "below"),
|
|
142
228
|
cross: makeBandNode("intersection", null),
|
|
143
|
-
last: { rowShown: false, colShown: false, crossShown: false, crossTint: "intersection", rowHeight: -1, colWidth: -1, crossWidth: -1, crossHeight: -1 },
|
|
229
|
+
last: { rowShown: false, colShown: false, crossShown: false, crossTint: "intersection", rowHeight: -1, colWidth: -1, crossWidth: -1, crossHeight: -1, rowLeftTransform: "", rowRightTransform: "", colAboveTransform: "", colBelowTransform: "", crossTransform: "" },
|
|
144
230
|
})
|
|
145
231
|
|
|
146
232
|
/** The group's nodes in DOM (= paint) order: segments under the intersection. / 帯組のノードを DOM (= 描画) 順に: セグメントの上に交点。 */
|
|
@@ -183,12 +269,15 @@ const setShown = (nodes: readonly HTMLDivElement[], shown: boolean): void => {
|
|
|
183
269
|
}
|
|
184
270
|
|
|
185
271
|
/**
|
|
186
|
-
* Per-frame band placement for one range rect in one layer (§2.4, partitioned BY INPUT)
|
|
272
|
+
* Per-frame band placement for one range rect in one layer (§2.4, partitioned BY INPUT).
|
|
273
|
+
* Every `transform` is same-value cached (census #23 extended — v0.9 plan §5.9 step 3): a
|
|
274
|
+
* byte-identical string is never rewritten, so quadrant clones that do not move on a pure
|
|
275
|
+
* cross-axis scroll write nothing.
|
|
187
276
|
* - row segments: `transform` only — left segment's right edge at `clamp(rect.left, 0, W)`
|
|
188
277
|
* (so `translate(clamp − W)` with its healed width W), right segment's left edge at
|
|
189
278
|
* `clamp(rect.right, 0, W)`; their height (cross-axis) is written only when it changes;
|
|
190
279
|
* - column segments: the transposed twin with H = clampHeight;
|
|
191
|
-
* - intersection: range-sized (size when changed, `transform`
|
|
280
|
+
* - intersection: range-sized (size when changed, `transform` when changed); when EXACTLY ONE
|
|
192
281
|
* family is shown and `highlight.intersection` is off, the node lights up tinted with that
|
|
193
282
|
* family's colour (inline `background` through the family's `--aqsp-band-*` variable) — the
|
|
194
283
|
* sheets-enhancer extension paints one full-width band THROUGH the selection when the other
|
|
@@ -200,11 +289,13 @@ const setShown = (nodes: readonly HTMLDivElement[], shown: boolean): void => {
|
|
|
200
289
|
* range's extent would tint the whole viewport on the other side (the sheets-enhancer
|
|
201
290
|
* extension draws nothing for whole-axis selections); `highlight` switches each family.
|
|
202
291
|
* Display is written only on a change (JS-side cache).
|
|
203
|
-
* レンジ矩形 1 つの層内配置 (毎フレーム、§2.4 の入力分割)
|
|
292
|
+
* レンジ矩形 1 つの層内配置 (毎フレーム、§2.4 の入力分割)。全 `transform` は同値キャッシュ
|
|
293
|
+
* (census #23 の拡張 — v0.9 計画 §5.9 手順 3): byte 恒等の文字列は再書込しないので、純横 / 純縦
|
|
294
|
+
* スクロールで動かない象限クローンは何も書かない。
|
|
204
295
|
* - 行セグメント: `transform` のみ — 左セグメントの右辺を `clamp(rect.left, 0, W)` へ (heal 済み幅 W で
|
|
205
296
|
* `translate(clamp − W)`)、右セグメントの左辺を `clamp(rect.right, 0, W)` へ。高さ (交差軸) は変化時だけ。
|
|
206
297
|
* - 列セグメント: H = clampHeight の転置双子。
|
|
207
|
-
* - 交点: レンジサイズ (サイズは変化時、`transform`
|
|
298
|
+
* - 交点: レンジサイズ (サイズは変化時、`transform` も変化時)。**単一族のみ**表示かつ intersection
|
|
208
299
|
* off のときはその族の色 (族の `--aqsp-band-*` 変数を参照するインライン `background`) で点灯する —
|
|
209
300
|
* sheets-enhancer 拡張は片族 off なら選択を貫く全幅帯 1 本を描くため、両脇セグメントだけではレンジ
|
|
210
301
|
* 位置で帯色が途切れる。両族表示時は拡張同様に穴のまま。
|
|
@@ -228,9 +319,19 @@ export const placeBandSegments = (nodes: BandNodes, rect: BandRect, extents: Ban
|
|
|
228
319
|
nodes.rowLeft.style.height = height
|
|
229
320
|
nodes.rowRight.style.height = height
|
|
230
321
|
}
|
|
231
|
-
// 主軸は transform のみ — 内側の辺をクランプ範囲へ置く (主軸サイズは heal パスの clampWidth)
|
|
232
|
-
|
|
233
|
-
|
|
322
|
+
// 主軸は transform のみ — 内側の辺をクランプ範囲へ置く (主軸サイズは heal パスの clampWidth)。
|
|
323
|
+
// transform も同値キャッシュ (census #23 の拡張 — v0.9 計画 §5.9 手順 3 第 1 段リトリート):
|
|
324
|
+
// 純横 / 純縦スクロールで動かない象限クローンの byte 恒等文字列は再書込しない
|
|
325
|
+
const rowLeftTransform = `translate(${clampTo(rect.left, extents.clampWidth) - extents.clampWidth}px, ${rect.top}px)`
|
|
326
|
+
if (rowLeftTransform !== last.rowLeftTransform) {
|
|
327
|
+
last.rowLeftTransform = rowLeftTransform
|
|
328
|
+
nodes.rowLeft.style.transform = rowLeftTransform
|
|
329
|
+
}
|
|
330
|
+
const rowRightTransform = `translate(${clampTo(rect.left + rect.width, extents.clampWidth)}px, ${rect.top}px)`
|
|
331
|
+
if (rowRightTransform !== last.rowRightTransform) {
|
|
332
|
+
last.rowRightTransform = rowRightTransform
|
|
333
|
+
nodes.rowRight.style.transform = rowRightTransform
|
|
334
|
+
}
|
|
234
335
|
}
|
|
235
336
|
// 列帯: 行帯と同じ全軸スパン省略
|
|
236
337
|
const colShown = highlight.column && !spansAxis
|
|
@@ -245,8 +346,17 @@ export const placeBandSegments = (nodes: BandNodes, rect: BandRect, extents: Ban
|
|
|
245
346
|
nodes.colAbove.style.width = width
|
|
246
347
|
nodes.colBelow.style.width = width
|
|
247
348
|
}
|
|
248
|
-
|
|
249
|
-
|
|
349
|
+
// 列帯も transform は同値キャッシュ (census #23 拡張 — 行帯と同じ規律)
|
|
350
|
+
const colAboveTransform = `translate(${rect.left}px, ${clampTo(rect.top, extents.clampHeight) - extents.clampHeight}px)`
|
|
351
|
+
if (colAboveTransform !== last.colAboveTransform) {
|
|
352
|
+
last.colAboveTransform = colAboveTransform
|
|
353
|
+
nodes.colAbove.style.transform = colAboveTransform
|
|
354
|
+
}
|
|
355
|
+
const colBelowTransform = `translate(${rect.left}px, ${clampTo(rect.top + rect.height, extents.clampHeight)}px)`
|
|
356
|
+
if (colBelowTransform !== last.colBelowTransform) {
|
|
357
|
+
last.colBelowTransform = colBelowTransform
|
|
358
|
+
nodes.colBelow.style.transform = colBelowTransform
|
|
359
|
+
}
|
|
250
360
|
}
|
|
251
361
|
// 交点: レンジそのもの (サイズは変化時、transform は毎フレーム)。単一族のみ表示で intersection が
|
|
252
362
|
// off のときもその族の色で点灯する — 拡張は片族 off なら選択を貫く全幅帯 1 本を描くため、両脇
|
|
@@ -269,7 +379,12 @@ export const placeBandSegments = (nodes: BandNodes, rect: BandRect, extents: Ban
|
|
|
269
379
|
nodes.cross.style.width = `${rect.width}px`
|
|
270
380
|
nodes.cross.style.height = `${rect.height}px`
|
|
271
381
|
}
|
|
272
|
-
|
|
382
|
+
// 交点 transform も同値キャッシュ (census #23 拡張)
|
|
383
|
+
const crossTransform = `translate(${rect.left}px, ${rect.top}px)`
|
|
384
|
+
if (crossTransform !== last.crossTransform) {
|
|
385
|
+
last.crossTransform = crossTransform
|
|
386
|
+
nodes.cross.style.transform = crossTransform
|
|
387
|
+
}
|
|
273
388
|
}
|
|
274
389
|
}
|
|
275
390
|
|
|
@@ -365,6 +480,15 @@ type SelectionOverlayProps = {
|
|
|
365
480
|
frozenCols: number
|
|
366
481
|
/** Frozen leading rows (v0.6 — drives the vertical split into 2D quadrants). / 先頭凍結行数 (v0.6 — 縦分割で 2D 象限化)。 */
|
|
367
482
|
frozenRows: number
|
|
483
|
+
/**
|
|
484
|
+
* v0.9 capability: trailing frozen columns (drives the horizontal split's t band; freshness
|
|
485
|
+
* reads still use the handle). Undeclared ⇒ the v0.8 x-split byte-identically (zero t quads).
|
|
486
|
+
* v0.9 能力: 末尾凍結列数 (横分割の t 帯。鮮度読みはハンドル)。未宣言 ⇒ v0.8 の x 分割と
|
|
487
|
+
* byte 恒等 (t 象限 0)。
|
|
488
|
+
*/
|
|
489
|
+
trailingCols?: number
|
|
490
|
+
/** v0.9 capability: trailing frozen rows (the vertical twin of trailingCols). / v0.9 能力: 末尾凍結行数 (trailingCols の縦双子)。 */
|
|
491
|
+
trailingRows?: number
|
|
368
492
|
sinkAriaLabel: string
|
|
369
493
|
onSinkMouseDown: (event: MouseEvent) => void
|
|
370
494
|
/**
|
|
@@ -390,8 +514,27 @@ type SelectionOverlayProps = {
|
|
|
390
514
|
tipAnchorRef?: RefObject<HTMLDivElement | null>
|
|
391
515
|
}
|
|
392
516
|
|
|
393
|
-
/**
|
|
394
|
-
|
|
517
|
+
/**
|
|
518
|
+
* Live resize guideline descriptor (logical axis px). `anchor` is REQUIRED (v0.9 §2.6 — no
|
|
519
|
+
* optional / default per Strict No-Fallback): "leading" takes the byte-identical v0.8 path
|
|
520
|
+
* (all-quadrant paint, no clamp); "trailing" paints only in quads whose gesture-axis kind is
|
|
521
|
+
* `t` with the band-local position clamped `max(0, local)`.
|
|
522
|
+
* リサイズガイドラインの記述子 (論理軸 px)。`anchor` は必須 (v0.9 §2.6 — Strict No-Fallback に
|
|
523
|
+
* つき optional / default なし): "leading" は v0.8 と byte 恒等の経路 (全象限描画・無クランプ)、
|
|
524
|
+
* "trailing" はジェスチャ軸 kind = t の象限のみ + band-local を `max(0, local)` へクランプ。
|
|
525
|
+
*/
|
|
526
|
+
export type ResizeGuide = { axis: "row" | "col"; position: number; anchor: "leading" | "trailing" }
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* One fill-pool entry: the node plus its last-written numbers (v0.9 §2.5 census #23 — the
|
|
530
|
+
* `placeBandSegments` last-value cache mirrored onto fills, `transform` included since the
|
|
531
|
+
* plan §5.9 step 3 first-stage retreat: a steady scroll frame writes only the transforms that
|
|
532
|
+
* actually changed; no DOM reads on the hot path).
|
|
533
|
+
* fill プールの 1 エントリ: ノード + 直近書込値 (v0.9 §2.5 census #23 — `placeBandSegments` の
|
|
534
|
+
* 同値キャッシュを fill へミラー。計画 §5.9 手順 3 第 1 段リトリートで `transform` も対象 —
|
|
535
|
+
* 定常スクロールフレームの書込は実際に変わった transform だけ、ホットパスで DOM を読まない)。
|
|
536
|
+
*/
|
|
537
|
+
type FillSlot = { node: HTMLDivElement; last: { shown: boolean; width: number; height: number; transform: string } }
|
|
395
538
|
|
|
396
539
|
/** Resident node set of one overlay layer (v0.5 layer split; every layer owns a dedicated container). / overlay 1 層ぶんの常設ノード束 (v0.5 の層分割。全層が専用コンテナを持つ)。 */
|
|
397
540
|
type LayerNodes = {
|
|
@@ -401,7 +544,7 @@ type LayerNodes = {
|
|
|
401
544
|
marquee: HTMLDivElement | null
|
|
402
545
|
fillPreview: HTMLDivElement | null
|
|
403
546
|
resizeGuide: HTMLDivElement | null
|
|
404
|
-
fillPool:
|
|
547
|
+
fillPool: FillSlot[]
|
|
405
548
|
/** Band groups per confirmed range (tail BAND_RANGE_CAP), inserted under the fills. / 確定レンジ毎の帯組 (末尾 BAND_RANGE_CAP 件)、fill の下。 */
|
|
406
549
|
bandPool: BandNodes[]
|
|
407
550
|
/** Band group of the live drag rect (null until a drag needs one). / ライブドラッグ矩形の帯組 (必要になるまで null)。 */
|
|
@@ -409,32 +552,107 @@ type LayerNodes = {
|
|
|
409
552
|
/** Last healed clamp extents of this layer's bands (-1 = never healed). / この層の帯の直近 heal 済みクランプ範囲 (-1 = 未 heal)。 */
|
|
410
553
|
bandClampWidth: number
|
|
411
554
|
bandClampHeight: number
|
|
555
|
+
/** Mounted divider segments in fixed slot order [x-leading, x-trailing, y-leading, y-trailing] = JSX (DOM) order (v0.9 §2.4). / マウント済み分割線セグメント (固定スロット順 [x-leading, x-trailing, y-leading, y-trailing] = JSX (DOM) 順 — v0.9 §2.4)。 */
|
|
556
|
+
dividerSlots: (HTMLDivElement | null)[]
|
|
557
|
+
/** Last-written shown state per divider slot (equality-suppressed `display` heal — v0.9 §2.4). / 分割線スロット毎の直近書込 shown 状態 (等値抑制 `display` heal — v0.9 §2.4)。 */
|
|
558
|
+
dividerShown: boolean[]
|
|
559
|
+
/** Identity-stable compact array of the quad's mounted segments (≤ 4, DOM order — the `chromeFloorOf` source, v0.9 §2.5). / 象限のマウント済みセグメントの identity 安定な圧縮配列 (≤ 4、DOM 順 — `chromeFloorOf` の源、v0.9 §2.5)。 */
|
|
560
|
+
dividers: HTMLDivElement[]
|
|
412
561
|
}
|
|
413
562
|
|
|
563
|
+
/**
|
|
564
|
+
* The canonical "first static chrome child" anchor of a quadrant (v0.9 §2.5 — ADR-24): the
|
|
565
|
+
* quad's first mounted divider segment, else the active frame. A structural ordering definition
|
|
566
|
+
* with a single source shared by BOTH dynamic insertion anchors (fill pool census #24 / band
|
|
567
|
+
* groups census #25) — not a value fallback. A fill or band created AFTER dividers exist must
|
|
568
|
+
* still land BELOW them, or the divider's seam absorption breaks.
|
|
569
|
+
* 象限の「最初の静的クローム子」正準アンカー (v0.9 §2.5 — ADR-24): マウント済み分割線セグメントの
|
|
570
|
+
* 先頭、無ければアクティブ枠。動的挿入アンカー 2 つ (fill プール census #24 / 帯組 census #25) が
|
|
571
|
+
* 共有する構造的順序定義の単一源 — 値フォールバックではない。分割線マウント後に生成された fill /
|
|
572
|
+
* 帯もその下へ着地しないと、分割線の継ぎ目吸収が破れる。
|
|
573
|
+
*/
|
|
574
|
+
const chromeFloorOf = (nodes: LayerNodes): HTMLDivElement | null => nodes.dividers[0] ?? nodes.active
|
|
575
|
+
|
|
414
576
|
/** Inline container geometry of one x-split ("" clears the property). / x 分割 1 本のコンテナインライン形状 ("" はプロパティ解除)。 */
|
|
415
577
|
type XSplitStyle = { left: string; right: string; width: string }
|
|
416
578
|
|
|
417
579
|
/** Inline container geometry of one y-split ("" clears the property). / y 分割 1 本のコンテナインライン形状 ("" はプロパティ解除)。 */
|
|
418
580
|
type YSplitStyle = { top: string; bottom: string; height: string }
|
|
419
581
|
|
|
420
|
-
/**
|
|
421
|
-
type
|
|
582
|
+
/** Axis band kind (v0.9 §2.5): m = unsplit axis, f = leading frozen, s = scroll, t = trailing frozen. / 軸帯の種別 (v0.9 §2.5): m = 未分割軸、f = 先頭凍結、s = スクロール、t = 末尾凍結。 */
|
|
583
|
+
type AxisKind = "m" | "f" | "s" | "t"
|
|
584
|
+
|
|
585
|
+
/** One write-pass layer context: nodes + axis kinds + tree→viewport maps + clamp extents + container inline geometry on both axes + seam flags (v0.6 2D quadrants; v0.9 adds the kinds for the guide's t-only branch and the seam flags for the marquee's edge-ownership rule). / 1 書込パスの層コンテキスト (ノード束 + 軸種別 + 両軸の木→viewport 写像 + クランプ範囲 + コンテナのインライン形状 + 継ぎ目フラグ。v0.6 の 2D 象限、v0.9 でガイドの t 限定分岐用に種別を、マーキーの縁所有権規則用に継ぎ目フラグを追加)。 */
|
|
586
|
+
type LayerCtx = {
|
|
587
|
+
nodes: LayerNodes
|
|
588
|
+
xKind: AxisKind
|
|
589
|
+
yKind: AxisKind
|
|
590
|
+
mapX: (treeX: number) => number
|
|
591
|
+
clampWidth: number
|
|
592
|
+
xStyle: XSplitStyle
|
|
593
|
+
xSeamLead: boolean
|
|
594
|
+
xSeamTrail: boolean
|
|
595
|
+
mapY: (treeY: number) => number
|
|
596
|
+
clampHeight: number
|
|
597
|
+
yStyle: YSplitStyle
|
|
598
|
+
ySeamLead: boolean
|
|
599
|
+
ySeamTrail: boolean
|
|
600
|
+
}
|
|
422
601
|
|
|
423
|
-
/**
|
|
424
|
-
|
|
602
|
+
/**
|
|
603
|
+
* One-axis split segment: kind + tree→viewport map + clamp extent + container inline geometry +
|
|
604
|
+
* seam flags. `seamLead` / `seamTrail` mark the clip edges where ANOTHER band adjoins (a pane
|
|
605
|
+
* seam): f trails into s, s leads from f (F > 0) and trails into t (T > 0), t leads from s; an
|
|
606
|
+
* unsplit m axis and the viewport edges have no seams. Static per split-cache build — the
|
|
607
|
+
* marquee's edge-ownership rule reads them per frame without allocating.
|
|
608
|
+
* 片軸の分割セグメント (種別 + 木→viewport 写像 + クランプ範囲 + コンテナのインライン形状 +
|
|
609
|
+
* 継ぎ目フラグ)。`seamLead` / `seamTrail` は**別の帯が隣接する**クリップ辺 (ペイン継ぎ目) の印 —
|
|
610
|
+
* f の後端は s、s の先端は f (F > 0)・後端は t (T > 0)、t の先端は s。未分割 m 軸と viewport 端は
|
|
611
|
+
* 継ぎ目なし。分割キャッシュ構築時の静的値 — マーキーの縁所有権規則が毎フレーム無確保で読む。
|
|
612
|
+
*/
|
|
613
|
+
type AxisSplit<S> = { kind: AxisKind; map: (tree: number) => number; clamp: number; style: S; seamLead: boolean; seamTrail: boolean }
|
|
425
614
|
|
|
426
|
-
/** Split-descriptor cache: quadrant contexts keyed on the freeze/viewport geometry (scroll-free — the split maps read the live scroll snapshot). / 分割記述子キャッシュ:
|
|
427
|
-
type SplitCache = {
|
|
615
|
+
/** Split-descriptor cache: quadrant contexts keyed on the freeze/viewport geometry incl. the v0.9 trailing family (scroll-free — the split maps read the live scroll snapshot; a trailing track resize moves trailingWidth and rebuilds). / 分割記述子キャッシュ: 末尾族 (v0.9) 込みの凍結 / viewport 幾何をキーにした象限コンテキスト (スクロール非依存 — 写像は生スクロール快照を読む。末尾トラックのリサイズは trailingWidth 経由で再構築)。 */
|
|
616
|
+
type SplitCache = {
|
|
617
|
+
frozenCols: number
|
|
618
|
+
frozenWidth: number
|
|
619
|
+
frozenRows: number
|
|
620
|
+
frozenHeight: number
|
|
621
|
+
trailingCols: number
|
|
622
|
+
trailingWidth: number
|
|
623
|
+
trailingVisibleWidth: number
|
|
624
|
+
trailingRows: number
|
|
625
|
+
trailingHeight: number
|
|
626
|
+
trailingVisibleHeight: number
|
|
627
|
+
bodyWidth: number
|
|
628
|
+
bodyHeight: number
|
|
629
|
+
ctxs: LayerCtx[]
|
|
630
|
+
}
|
|
428
631
|
|
|
429
632
|
/** Main-band container geometry per axis (no freeze — module constants so a cache rebuild allocates none). / 各軸の主帯コンテナ形状 (非凍結 — キャッシュ再構築でも確保しないモジュール定数)。 */
|
|
430
633
|
const X_MAIN_STYLE: XSplitStyle = { left: "0px", right: "0px", width: "" }
|
|
431
634
|
const Y_MAIN_STYLE: YSplitStyle = { top: "0px", bottom: "0px", height: "" }
|
|
432
635
|
|
|
433
636
|
/** Empty resident-node set. / 空の常設ノード束。 */
|
|
434
|
-
const emptyLayerNodes = (): LayerNodes => ({
|
|
637
|
+
const emptyLayerNodes = (): LayerNodes => ({
|
|
638
|
+
container: null,
|
|
639
|
+
active: null,
|
|
640
|
+
dragRect: null,
|
|
641
|
+
marquee: null,
|
|
642
|
+
fillPreview: null,
|
|
643
|
+
resizeGuide: null,
|
|
644
|
+
fillPool: [],
|
|
645
|
+
bandPool: [],
|
|
646
|
+
dragBand: null,
|
|
647
|
+
bandClampWidth: -1,
|
|
648
|
+
bandClampHeight: -1,
|
|
649
|
+
dividerSlots: [null, null, null, null],
|
|
650
|
+
dividerShown: [false, false, false, false],
|
|
651
|
+
dividers: [],
|
|
652
|
+
})
|
|
435
653
|
|
|
436
|
-
/** All quadrant keys ever rendered
|
|
437
|
-
const QUADRANT_KEYS = ["mm", "
|
|
654
|
+
/** All quadrant keys ever rendered — the full {m,f,s,t}² pre-seed (v0.9 §2.5: identity-stable ref-bundle life structure). / 描画され得る全象限キー — {m,f,s,t}² のフル事前シード (v0.9 §2.5: identity 安定な ref 束の生活構造)。 */
|
|
655
|
+
const QUADRANT_KEYS = ["mm", "mf", "ms", "mt", "fm", "ff", "fs", "ft", "sm", "sf", "ss", "st", "tm", "tf", "ts", "tt"] as const
|
|
438
656
|
|
|
439
657
|
/** Fill-handle square size in px (§8.1). / fill ハンドルの一辺 (px)。 */
|
|
440
658
|
const FILL_HANDLE_SIZE = 6
|
|
@@ -462,6 +680,8 @@ export const SelectionOverlay = ({
|
|
|
462
680
|
enableFillHandle,
|
|
463
681
|
frozenCols,
|
|
464
682
|
frozenRows,
|
|
683
|
+
trailingCols,
|
|
684
|
+
trailingRows,
|
|
465
685
|
sinkAriaLabel,
|
|
466
686
|
onSinkMouseDown,
|
|
467
687
|
editPaintRef,
|
|
@@ -541,6 +761,29 @@ export const SelectionOverlay = ({
|
|
|
541
761
|
healBandGeometry(nodes.dragBand, clampWidth, clampHeight)
|
|
542
762
|
}
|
|
543
763
|
}, [])
|
|
764
|
+
/**
|
|
765
|
+
* Live axis models for the cached t maps (render-time refresh — a build-time closure capture
|
|
766
|
+
* would go stale across a sheet switch whose freeze/viewport cache key happens to match).
|
|
767
|
+
* キャッシュ済み t 写像が読む軸モデルの生 ref (レンダー毎更新 — 構築時の閉包捕捉は、凍結 /
|
|
768
|
+
* viewport キーが偶然一致するシート切替で stale になる)。
|
|
769
|
+
*/
|
|
770
|
+
const axesRef = useRef({ rowAxis, colAxis })
|
|
771
|
+
axesRef.current = { rowAxis, colAxis }
|
|
772
|
+
/**
|
|
773
|
+
* Equality-suppressed `display` heal of one divider slot (v0.9 §2.4): visibility is
|
|
774
|
+
* band-px-driven per write pass, written ONLY on a flip — the "zero divider style writes
|
|
775
|
+
* during a steady wheel window" invariant holds by construction.
|
|
776
|
+
* 分割線スロット 1 つの等値抑制 `display` heal (v0.9 §2.4): 可視は書込パス毎の帯 px 駆動で、
|
|
777
|
+
* 書くのはフリップ時だけ — 「定常 wheel 窓中の分割線 style 書込 = 0」不変量が構造で立つ。
|
|
778
|
+
*/
|
|
779
|
+
const healDivider = useCallback((nodes: LayerNodes, slot: number, shown: boolean) => {
|
|
780
|
+
const node = nodes.dividerSlots[slot]
|
|
781
|
+
if (node === null || nodes.dividerShown[slot] === shown) {
|
|
782
|
+
return
|
|
783
|
+
}
|
|
784
|
+
nodes.dividerShown[slot] = shown
|
|
785
|
+
node.style.display = shown ? "" : "none"
|
|
786
|
+
}, [])
|
|
544
787
|
/** Live scroll snapshot the cached split maps read (`layersFor` refreshes it on every call). / キャッシュ済み分割写像が読む生スクロール快照 (`layersFor` が毎呼び更新)。 */
|
|
545
788
|
const scrollSnapshotRef = useRef({ x: 0, y: 0 })
|
|
546
789
|
/** Split-descriptor cache — invalidated by freeze / viewport geometry change, never by scroll. / 分割記述子キャッシュ — 凍結 / viewport 幾何の変化で無効化 (スクロールでは無効化しない)。 */
|
|
@@ -558,7 +801,7 @@ export const SelectionOverlay = ({
|
|
|
558
801
|
const layersFor = useCallback(
|
|
559
802
|
(hx: number, vy: number): LayerCtx[] => {
|
|
560
803
|
const handle = handleRef.current
|
|
561
|
-
const frozen = handle?.getFrozenSize() ??
|
|
804
|
+
const frozen = handle?.getFrozenSize() ?? EMPTY_FROZEN_SIZE
|
|
562
805
|
const viewport = handle?.getViewportSize()
|
|
563
806
|
const bodyWidth = viewport?.width ?? -1
|
|
564
807
|
const bodyHeight = viewport?.height ?? -1
|
|
@@ -567,42 +810,128 @@ export const SelectionOverlay = ({
|
|
|
567
810
|
scroll.x = hx
|
|
568
811
|
scroll.y = vy
|
|
569
812
|
let cache = splitCacheRef.current
|
|
570
|
-
if (
|
|
813
|
+
if (
|
|
814
|
+
cache === null ||
|
|
815
|
+
cache.frozenCols !== frozen.cols ||
|
|
816
|
+
cache.frozenWidth !== frozen.width ||
|
|
817
|
+
cache.frozenRows !== frozen.rows ||
|
|
818
|
+
cache.frozenHeight !== frozen.height ||
|
|
819
|
+
cache.trailingCols !== frozen.trailingCols ||
|
|
820
|
+
cache.trailingWidth !== frozen.trailingWidth ||
|
|
821
|
+
cache.trailingVisibleWidth !== frozen.trailingVisibleWidth ||
|
|
822
|
+
cache.trailingRows !== frozen.trailingRows ||
|
|
823
|
+
cache.trailingHeight !== frozen.trailingHeight ||
|
|
824
|
+
cache.trailingVisibleHeight !== frozen.trailingVisibleHeight ||
|
|
825
|
+
cache.bodyWidth !== bodyWidth ||
|
|
826
|
+
cache.bodyHeight !== bodyHeight
|
|
827
|
+
) {
|
|
571
828
|
// 幾何が変わったときだけ記述子を再構築 (最熱経路の毎 repaint 閉包 / spread / 配列確保を
|
|
572
|
-
// 断つ GC 圧対策)。x
|
|
573
|
-
// [
|
|
829
|
+
// 断つ GC 圧対策)。x 軸分割 (v0.9 §2.5): 両凍結なしは主帯 1 本、それ以外は
|
|
830
|
+
// [f?] + [s] + [t?] — 末尾のみの軸は s + t (kind m は「未分割軸」の意を保つ)。
|
|
831
|
+
// vis = handle の trailingVisible* (ADR-19 の単一源 — 書込パスはフィールド名で引用)
|
|
832
|
+
const visX = frozen.trailingVisibleWidth
|
|
833
|
+
const visY = frozen.trailingVisibleHeight
|
|
834
|
+
// 継ぎ目フラグの導出 (縁所有権規則の読み先): 帯クリップは軸を互いに素な半開区間
|
|
835
|
+
// f [0, W_F) / s [W_F, extent − vis) / t [extent − vis, extent) に分割する — 別の帯が
|
|
836
|
+
// 隣接する辺だけが継ぎ目 (m 軸と viewport 端は継ぎ目なし)。s の両端は F / T の実在で決まる
|
|
574
837
|
const xSplits: AxisSplit<XSplitStyle>[] =
|
|
575
|
-
frozen.cols <= 0
|
|
576
|
-
? [{ kind: "m", map: (treeX) => treeX - scroll.x, clamp: bodyWidth, style: X_MAIN_STYLE }]
|
|
838
|
+
frozen.cols <= 0 && frozen.trailingCols <= 0
|
|
839
|
+
? [{ kind: "m", map: (treeX) => treeX - scroll.x, clamp: bodyWidth, style: X_MAIN_STYLE, seamLead: false, seamTrail: false }]
|
|
577
840
|
: [
|
|
578
|
-
{ kind: "f", map: (treeX) => treeX, clamp: frozen.width, style: { left: "0px", width: `${frozen.width}px`, right: "" } },
|
|
579
|
-
|
|
841
|
+
...(frozen.cols > 0 ? [{ kind: "f" as const, map: (treeX: number) => treeX, clamp: frozen.width, style: { left: "0px", width: `${frozen.width}px`, right: "" }, seamLead: false, seamTrail: true }] : []),
|
|
842
|
+
// s 帯クリップ [W_F, extent − W_T_vis) — clamp = body − W_F − W_T_vis、
|
|
843
|
+
// right = W_T_vis (導出: v0.9 §2.5 の s 変形。T = 0 は vis 0 で v0.8 と byte 一致)
|
|
844
|
+
{
|
|
845
|
+
kind: "s" as const,
|
|
846
|
+
map: (treeX: number) => treeX - scroll.x - frozen.width,
|
|
847
|
+
clamp: Math.max(0, bodyWidth - frozen.width - visX),
|
|
848
|
+
style: { left: `${frozen.width}px`, right: `${visX}px`, width: "" },
|
|
849
|
+
seamLead: frozen.cols > 0,
|
|
850
|
+
seamTrail: frozen.trailingCols > 0,
|
|
851
|
+
},
|
|
852
|
+
// t 帯クリップ [extent − W_T_vis, extent)。band-local 写像の導出 (v0.9 §2.5):
|
|
853
|
+
// エンジン順写像 viewport x = extent − (totalSize − treeX)、象限原点 = extent − vis
|
|
854
|
+
// ⇒ band-local = treeX − totalSize + vis (tree 列 colCount − T の left は vis − W_T
|
|
855
|
+
// に着地 — クリップ左詰めの正しさの値ピン §5.4)。totalSize は axesRef 経由の生読み
|
|
856
|
+
// (スクロール帯トラックのリサイズはキャッシュキー外 — 入力 treeX が鮮度軸で来る
|
|
857
|
+
// ため total も鮮度で読む。W_T / vis の変化はキャッシュキーが再構築する)
|
|
858
|
+
...(frozen.trailingCols > 0 ? [{ kind: "t" as const, map: (treeX: number) => treeX - axesRef.current.colAxis.totalSize() + visX, clamp: visX, style: { left: "", right: "0px", width: `${visX}px` }, seamLead: true, seamTrail: false }] : []),
|
|
580
859
|
]
|
|
581
860
|
const ySplits: AxisSplit<YSplitStyle>[] =
|
|
582
|
-
frozen.rows <= 0
|
|
583
|
-
? [{ kind: "m", map: (treeY) => treeY - scroll.y, clamp: bodyHeight, style: Y_MAIN_STYLE }]
|
|
861
|
+
frozen.rows <= 0 && frozen.trailingRows <= 0
|
|
862
|
+
? [{ kind: "m", map: (treeY) => treeY - scroll.y, clamp: bodyHeight, style: Y_MAIN_STYLE, seamLead: false, seamTrail: false }]
|
|
584
863
|
: [
|
|
585
|
-
{ kind: "f", map: (treeY) => treeY, clamp: frozen.height, style: { top: "0px", height: `${frozen.height}px`, bottom: "" } },
|
|
586
|
-
|
|
864
|
+
...(frozen.rows > 0 ? [{ kind: "f" as const, map: (treeY: number) => treeY, clamp: frozen.height, style: { top: "0px", height: `${frozen.height}px`, bottom: "" }, seamLead: false, seamTrail: true }] : []),
|
|
865
|
+
// s 帯クリップ [H_F, extent − H_T_vis) — x の転置双子 (v0.9 §2.5)
|
|
866
|
+
{
|
|
867
|
+
kind: "s" as const,
|
|
868
|
+
map: (treeY: number) => treeY - scroll.y - frozen.height,
|
|
869
|
+
clamp: Math.max(0, bodyHeight - frozen.height - visY),
|
|
870
|
+
style: { top: `${frozen.height}px`, bottom: `${visY}px`, height: "" },
|
|
871
|
+
seamLead: frozen.rows > 0,
|
|
872
|
+
seamTrail: frozen.trailingRows > 0,
|
|
873
|
+
},
|
|
874
|
+
// t 帯 (bottom-anchored の行双子): band-local = treeY − totalSize + vis (v0.9 §2.5)
|
|
875
|
+
...(frozen.trailingRows > 0 ? [{ kind: "t" as const, map: (treeY: number) => treeY - axesRef.current.rowAxis.totalSize() + visY, clamp: visY, style: { top: "", bottom: "0px", height: `${visY}px` }, seamLead: true, seamTrail: false }] : []),
|
|
587
876
|
]
|
|
588
877
|
const ctxs: LayerCtx[] = []
|
|
589
878
|
for (const xs of xSplits) {
|
|
590
879
|
for (const ys of ySplits) {
|
|
591
|
-
ctxs.push({
|
|
880
|
+
ctxs.push({
|
|
881
|
+
nodes: nodeSetFor(`${xs.kind}${ys.kind}`),
|
|
882
|
+
xKind: xs.kind,
|
|
883
|
+
yKind: ys.kind,
|
|
884
|
+
mapX: xs.map,
|
|
885
|
+
clampWidth: xs.clamp,
|
|
886
|
+
xStyle: xs.style,
|
|
887
|
+
xSeamLead: xs.seamLead,
|
|
888
|
+
xSeamTrail: xs.seamTrail,
|
|
889
|
+
mapY: ys.map,
|
|
890
|
+
clampHeight: ys.clamp,
|
|
891
|
+
yStyle: ys.style,
|
|
892
|
+
ySeamLead: ys.seamLead,
|
|
893
|
+
ySeamTrail: ys.seamTrail,
|
|
894
|
+
})
|
|
592
895
|
}
|
|
593
896
|
}
|
|
594
|
-
cache = {
|
|
897
|
+
cache = {
|
|
898
|
+
frozenCols: frozen.cols,
|
|
899
|
+
frozenWidth: frozen.width,
|
|
900
|
+
frozenRows: frozen.rows,
|
|
901
|
+
frozenHeight: frozen.height,
|
|
902
|
+
trailingCols: frozen.trailingCols,
|
|
903
|
+
trailingWidth: frozen.trailingWidth,
|
|
904
|
+
trailingVisibleWidth: frozen.trailingVisibleWidth,
|
|
905
|
+
trailingRows: frozen.trailingRows,
|
|
906
|
+
trailingHeight: frozen.trailingHeight,
|
|
907
|
+
trailingVisibleHeight: frozen.trailingVisibleHeight,
|
|
908
|
+
bodyWidth,
|
|
909
|
+
bodyHeight,
|
|
910
|
+
ctxs,
|
|
911
|
+
}
|
|
595
912
|
splitCacheRef.current = cache
|
|
596
913
|
}
|
|
914
|
+
// 分割線の可視述語 (v0.9 §2.4 の表 — スロット固定順 [x-leading, x-trailing, y-leading,
|
|
915
|
+
// y-trailing] = [W_F, W_T, H_F, H_T] の TREE px > 0。全トラック非表示 (px = 0) で消灯、
|
|
916
|
+
// overlap 縮退の消滅は象限クリップが構造で担う)
|
|
917
|
+
const showXLead = frozen.width > 0
|
|
918
|
+
const showXTrail = frozen.trailingWidth > 0
|
|
919
|
+
const showYLead = frozen.height > 0
|
|
920
|
+
const showYTrail = frozen.trailingHeight > 0
|
|
597
921
|
for (const ctx of cache.ctxs) {
|
|
598
922
|
// ヒールは毎呼び (遅延 attach / 象限切替の新コンテナへも形状が届く — 同値抑止つき)
|
|
599
923
|
healContainer(ctx.nodes.container, ctx.xStyle, ctx.yStyle)
|
|
600
924
|
// 帯セグメントの主軸サイズはコンテナと同じ契機でヒール (§2.4 — 空プールなら無書込)
|
|
601
925
|
healLayerBands(ctx.nodes, ctx.clampWidth, ctx.clampHeight)
|
|
926
|
+
// 分割線セグメント可視も同パスでヒール (v0.9 §2.4 / §4.10 #14 — 等値抑制つき)
|
|
927
|
+
healDivider(ctx.nodes, 0, showXLead)
|
|
928
|
+
healDivider(ctx.nodes, 1, showXTrail)
|
|
929
|
+
healDivider(ctx.nodes, 2, showYLead)
|
|
930
|
+
healDivider(ctx.nodes, 3, showYTrail)
|
|
602
931
|
}
|
|
603
932
|
return cache.ctxs
|
|
604
933
|
},
|
|
605
|
-
[handleRef, nodeSetFor, healContainer, healLayerBands],
|
|
934
|
+
[handleRef, nodeSetFor, healContainer, healLayerBands, healDivider],
|
|
606
935
|
)
|
|
607
936
|
/**
|
|
608
937
|
* Stable ref-callback set for one layer (❗ identity-stable via useMemo — a per-render
|
|
@@ -612,6 +941,28 @@ export const SelectionOverlay = ({
|
|
|
612
941
|
* React が毎回 detach / attach し、プールだけが空になって DOM ノードが残り、次の repaint で
|
|
613
942
|
* 塗りが二重化する)。
|
|
614
943
|
*/
|
|
944
|
+
/**
|
|
945
|
+
* Registers one divider segment into its fixed slot and rebuilds the compact `dividers`
|
|
946
|
+
* array in slot (= JSX = DOM) order (v0.9 §2.4 / §2.5). Runs only on mount / unmount of a
|
|
947
|
+
* segment — never per frame. The last-shown cache resets to the JSX initial state
|
|
948
|
+
* (`display: none`) so the first heal after a mount writes exactly once.
|
|
949
|
+
* 分割線セグメント 1 つを固定スロットへ登録し、圧縮配列 `dividers` をスロット (= JSX = DOM)
|
|
950
|
+
* 順で再構成 (v0.9 §2.4 / §2.5)。実行はセグメントのマウント / アンマウント時のみ — 毎フレーム
|
|
951
|
+
* では走らない。直近 shown キャッシュは JSX 初期状態 (`display: none`) へ戻し、マウント後の
|
|
952
|
+
* 最初の heal がちょうど 1 回書けるようにする。
|
|
953
|
+
*/
|
|
954
|
+
const setDividerSlot = useCallback((n: LayerNodes, slot: number, el: HTMLDivElement | null) => {
|
|
955
|
+
n.dividerSlots[slot] = el
|
|
956
|
+
// JSX 初期 display は "none" — 等値抑制キャッシュを初期値へ同期 (空砲抑制の防止)
|
|
957
|
+
n.dividerShown[slot] = false
|
|
958
|
+
// 圧縮配列は identity を保ったまま中身だけ再構成 (chromeFloorOf の読み先)
|
|
959
|
+
n.dividers.length = 0
|
|
960
|
+
for (const node of n.dividerSlots) {
|
|
961
|
+
if (node !== null) {
|
|
962
|
+
n.dividers.push(node)
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}, [])
|
|
615
966
|
const makeLayerRefs = useCallback(
|
|
616
967
|
(key: string) => {
|
|
617
968
|
const nodes = () => nodeSetFor(key)
|
|
@@ -628,6 +979,12 @@ export const SelectionOverlay = ({
|
|
|
628
979
|
n.bandClampHeight = -1
|
|
629
980
|
}
|
|
630
981
|
},
|
|
982
|
+
// 分割線 4 スロット (v0.9 §2.4 — 固定スロット順 = JSX 順。存在は件数駆動の JSX 分岐、
|
|
983
|
+
// React が mount / unmount で装着・解除する)
|
|
984
|
+
dividerXLeading: (el: HTMLDivElement | null) => setDividerSlot(nodes(), 0, el),
|
|
985
|
+
dividerXTrailing: (el: HTMLDivElement | null) => setDividerSlot(nodes(), 1, el),
|
|
986
|
+
dividerYLeading: (el: HTMLDivElement | null) => setDividerSlot(nodes(), 2, el),
|
|
987
|
+
dividerYTrailing: (el: HTMLDivElement | null) => setDividerSlot(nodes(), 3, el),
|
|
631
988
|
active: (el: HTMLDivElement | null) => {
|
|
632
989
|
nodes().active = el
|
|
633
990
|
},
|
|
@@ -645,7 +1002,7 @@ export const SelectionOverlay = ({
|
|
|
645
1002
|
},
|
|
646
1003
|
}
|
|
647
1004
|
},
|
|
648
|
-
[nodeSetFor],
|
|
1005
|
+
[nodeSetFor, setDividerSlot],
|
|
649
1006
|
)
|
|
650
1007
|
// 象限キーごとに identity 安定な ref 束を用意 (空 deps — 毎レンダー生成だと React が detach /
|
|
651
1008
|
// attach してプールが空になり塗りが二重化する)。JSX は実効 F / R に応じて必要な象限だけ描く
|
|
@@ -655,6 +1012,12 @@ export const SelectionOverlay = ({
|
|
|
655
1012
|
const overlayFrozenWidth = frozenWidthOf(colAxis, effectiveFrozenCols)
|
|
656
1013
|
const effectiveFrozenRows = Math.min(Math.max(0, frozenRows), rowAxis.count)
|
|
657
1014
|
const overlayFrozenHeight = frozenHeightOf(rowAxis, effectiveFrozenRows)
|
|
1015
|
+
// render 面の実効 T / W_T (v0.9 §2.2 の二値系双子 — エンジンクランプと byte 一致の leading 勝ち
|
|
1016
|
+
// min(max(0, T), count − F_eff))。未宣言は能力分岐で 0 (v0.8 の JSX 構造をそのまま再現)
|
|
1017
|
+
const effectiveTrailingCols = trailingCols === undefined ? 0 : Math.min(Math.max(0, trailingCols), colAxis.count - effectiveFrozenCols)
|
|
1018
|
+
const overlayTrailingWidth = trailingWidthOf(colAxis, effectiveTrailingCols)
|
|
1019
|
+
const effectiveTrailingRows = trailingRows === undefined ? 0 : Math.min(Math.max(0, trailingRows), rowAxis.count - effectiveFrozenRows)
|
|
1020
|
+
const overlayTrailingHeight = trailingHeightOf(rowAxis, effectiveTrailingRows)
|
|
658
1021
|
// 生存中のドラッグ矩形レンジ (null = 非表示)。writeDragFrame が書き、repaintOverlay がスクロール
|
|
659
1022
|
// 追随の再配置に読む — ドラッグ中ポインタ静止のままホイールすると、fill / アクティブ枠だけが
|
|
660
1023
|
// onScroll repaint で動き矩形が取り残される desync の対策
|
|
@@ -748,9 +1111,9 @@ export const SelectionOverlay = ({
|
|
|
748
1111
|
return highlight.row || highlight.column || highlight.intersection ? highlight : null
|
|
749
1112
|
}, [highlightRef])
|
|
750
1113
|
|
|
751
|
-
/** Inserts a band group under the layer's fills (before the first fill, else before the active
|
|
1114
|
+
/** Inserts a band group under the layer's fills (before the first fill, else before the chrome floor — v0.9 §2.5 census #25: the old `nodes.active` anchor could not land a band group below a mounted divider). / 帯組を層の fill の下へ挿入 (先頭 fill の手前、無ければ静的クローム床の手前 — v0.9 §2.5 census #25: 旧 `nodes.active` 直指しではマウント済み分割線の下へ帯組を入れられない)。 */
|
|
752
1115
|
const insertBandGroup = useCallback((container: HTMLDivElement, nodes: LayerNodes, group: BandNodes) => {
|
|
753
|
-
const before = nodes.fillPool
|
|
1116
|
+
const before = nodes.fillPool[0]?.node ?? chromeFloorOf(nodes)
|
|
754
1117
|
for (const node of bandNodeList(group)) {
|
|
755
1118
|
container.insertBefore(node, before)
|
|
756
1119
|
}
|
|
@@ -873,7 +1236,7 @@ export const SelectionOverlay = ({
|
|
|
873
1236
|
[rangeToLayerRect, layersFor, applyRect],
|
|
874
1237
|
)
|
|
875
1238
|
|
|
876
|
-
/** Positions the resize guideline nodes
|
|
1239
|
+
/** Positions the resize guideline nodes: a "leading" guide paints in ALL quadrants through each committed map (byte-identical v0.8 path, no clamp); a "trailing" guide paints ONLY in quads whose gesture-axis kind is `t`, with the band-local position clamped `max(0, local)` (v0.9 §2.6 / §4.10 #27). / ガイドライン配置: "leading" は全象限を各写像で描く (v0.8 と byte 恒等・無クランプ)、"trailing" はジェスチャ軸 kind = t の象限のみ + band-local を `max(0, local)` へクランプ (v0.9 §2.6 / §4.10 #27)。 */
|
|
877
1240
|
const positionResizeGuide = useCallback(
|
|
878
1241
|
(guide: ResizeGuide | null, vy: number, hx: number) => {
|
|
879
1242
|
for (const ctx of layersFor(hx, vy)) {
|
|
@@ -885,14 +1248,32 @@ export const SelectionOverlay = ({
|
|
|
885
1248
|
node.style.display = "none"
|
|
886
1249
|
continue
|
|
887
1250
|
}
|
|
888
|
-
|
|
1251
|
+
const trailing = guide.anchor === "trailing"
|
|
889
1252
|
if (guide.axis === "col") {
|
|
890
|
-
|
|
1253
|
+
// 末尾アンカーはジェスチャ軸 kind = t の象限のみ描く (v0.9 §2.6 — 末尾の予定位置を
|
|
1254
|
+
// s / f 写像へ通すと別の木領域で、最大スクロール付近に第 2 の線が出る)
|
|
1255
|
+
if (trailing && ctx.xKind !== "t") {
|
|
1256
|
+
node.style.display = "none"
|
|
1257
|
+
continue
|
|
1258
|
+
}
|
|
1259
|
+
node.style.display = "block"
|
|
1260
|
+
const localX = ctx.mapX(guide.position)
|
|
1261
|
+
// t 分岐のみ max(0, local) クランプ (v0.9 §2.6 — 帯先頭拡大の負 band-local は
|
|
1262
|
+
// overflow: hidden で全ジェスチャ不可視になるため t クリップ近端へピン。
|
|
1263
|
+
// leading 分岐は無クランプのまま = byte 恒等対照)
|
|
1264
|
+
node.style.transform = `translate(${trailing ? Math.max(0, localX) : localX}px, 0px)`
|
|
891
1265
|
node.style.width = "1px"
|
|
892
1266
|
node.style.height = "100%"
|
|
893
1267
|
} else {
|
|
894
|
-
|
|
895
|
-
|
|
1268
|
+
if (trailing && ctx.yKind !== "t") {
|
|
1269
|
+
node.style.display = "none"
|
|
1270
|
+
continue
|
|
1271
|
+
}
|
|
1272
|
+
node.style.display = "block"
|
|
1273
|
+
// 行線は各象限の mapY で写す (v0.6 — 凍結行帯では静的、スクロール帯では −vy−H_F。
|
|
1274
|
+
// v0.9: t 帯では bottom-anchored band-local、末尾アンカーのみ max(0, local))
|
|
1275
|
+
const localY = ctx.mapY(guide.position)
|
|
1276
|
+
node.style.transform = `translate(0px, ${trailing ? Math.max(0, localY) : localY}px)`
|
|
896
1277
|
node.style.width = "100%"
|
|
897
1278
|
node.style.height = "1px"
|
|
898
1279
|
}
|
|
@@ -928,7 +1309,7 @@ export const SelectionOverlay = ({
|
|
|
928
1309
|
* 保ったまま戻る。閉じたアンカーは加えて `inert` (PopoverLayer) なので閉じた層の DOM は不変。
|
|
929
1310
|
*/
|
|
930
1311
|
const placeAnchor = useCallback(
|
|
931
|
-
(node: HTMLDivElement | null, cell: CellCoord | null,
|
|
1312
|
+
(node: HTMLDivElement | null, cell: CellCoord | null, ctxX: AxisForwardCtx, ctxY: AxisForwardCtx, frozen: FrozenSnapshot, bodyWidth: number, viewportH: number) => {
|
|
932
1313
|
if (node === null) {
|
|
933
1314
|
return
|
|
934
1315
|
}
|
|
@@ -936,13 +1317,21 @@ export const SelectionOverlay = ({
|
|
|
936
1317
|
node.style.clipPath = ANCHOR_HIDDEN_CLIP
|
|
937
1318
|
return
|
|
938
1319
|
}
|
|
939
|
-
const rawTop = rowToViewportY(rowAxis, cell.row,
|
|
940
|
-
const rawLeft = colToViewportX(colAxis, cell.col,
|
|
1320
|
+
const rawTop = rowToViewportY(rowAxis, cell.row, ctxY)
|
|
1321
|
+
const rawLeft = colToViewportX(colAxis, cell.col, ctxX)
|
|
1322
|
+
const width = colAxis.sizeOf(cell.col)
|
|
1323
|
+
const height = rowAxis.sizeOf(cell.row)
|
|
941
1324
|
// 原点が viewport の右 / 下の外、またはスクロール軸のセルが凍結帯の下 (凍結なしは左 / 上の外) ⇒ 非表示
|
|
942
1325
|
const beyondViewport = rawLeft >= bodyWidth || rawTop >= viewportH
|
|
943
1326
|
const underFrozenCol = cell.col >= frozen.cols && rawLeft < frozen.width
|
|
944
1327
|
const underFrozenRow = cell.row >= frozen.rows && rawTop < frozen.height
|
|
945
|
-
|
|
1328
|
+
// 末尾帯への侵入 (v0.9 §4.10 #15 — `rawLeft < W_F` の正確なミラー): スクロール軸セルの
|
|
1329
|
+
// 遠端 rawLeft + 幅が帯始端 body − vis を越えたら非表示。第 1 結合子 col < count − T は
|
|
1330
|
+
// load-bearing の除外 — 末尾セル自身は帯内が正位置で、落とすと末尾セルのアンカーが全滅
|
|
1331
|
+
// する (§5.4 陽性ゲート / TF-7)。T = 0 は trailingWidth 0 で恒偽 (v0.8 恒等)
|
|
1332
|
+
const underTrailingCol = frozen.trailingWidth > 0 && cell.col < colAxis.count - frozen.trailingCols && rawLeft + width > bodyWidth - frozen.trailingVisibleWidth
|
|
1333
|
+
const underTrailingRow = frozen.trailingHeight > 0 && cell.row < rowAxis.count - frozen.trailingRows && rawTop + height > viewportH - frozen.trailingVisibleHeight
|
|
1334
|
+
if (beyondViewport || underFrozenCol || underFrozenRow || underTrailingCol || underTrailingRow) {
|
|
946
1335
|
node.style.clipPath = ANCHOR_HIDDEN_CLIP
|
|
947
1336
|
return
|
|
948
1337
|
}
|
|
@@ -950,8 +1339,6 @@ export const SelectionOverlay = ({
|
|
|
950
1339
|
node.style.clipPath = ""
|
|
951
1340
|
node.style.transform = `translate(${rawLeft}px, ${rawTop}px)`
|
|
952
1341
|
// 寸法は変化時だけ (署名比較 — 毎フレームは transform のみ)
|
|
953
|
-
const width = colAxis.sizeOf(cell.col)
|
|
954
|
-
const height = rowAxis.sizeOf(cell.row)
|
|
955
1342
|
const signature = `${width} ${height}`
|
|
956
1343
|
if (node.dataset.aqspAnchorSig !== signature) {
|
|
957
1344
|
node.dataset.aqspAnchorSig = signature
|
|
@@ -977,8 +1364,13 @@ export const SelectionOverlay = ({
|
|
|
977
1364
|
const anchors = popoverAnchors.current
|
|
978
1365
|
const { width: bodyWidth, height: viewportH } = handle.getViewportSize()
|
|
979
1366
|
const frozen = handle.getFrozenSize()
|
|
980
|
-
|
|
981
|
-
|
|
1367
|
+
// 判断時 forward ctx (v0.9 §2.1 — census #15): アンカー配置の呼出元で 1 ハンドル
|
|
1368
|
+
// スナップショットから両軸をその場インライン構築 (跨サイト共有ビルダ禁止 — §5.3。
|
|
1369
|
+
// extent は同スナップショット既読の getViewportSize())
|
|
1370
|
+
const anchorCtxX: AxisForwardCtx = { scroll: hx, frozenCount: frozen.cols, trailingCount: frozen.trailingCols, extent: bodyWidth }
|
|
1371
|
+
const anchorCtxY: AxisForwardCtx = { scroll: vy, frozenCount: frozen.rows, trailingCount: frozen.trailingRows, extent: viewportH }
|
|
1372
|
+
placeAnchor(interactiveAnchorRef === undefined ? null : interactiveAnchorRef.current, anchors.interactive, anchorCtxX, anchorCtxY, frozen, bodyWidth, viewportH)
|
|
1373
|
+
placeAnchor(tipAnchorRef === undefined ? null : tipAnchorRef.current, anchors.tip, anchorCtxX, anchorCtxY, frozen, bodyWidth, viewportH)
|
|
982
1374
|
},
|
|
983
1375
|
[popoverAnchors, interactiveAnchorRef, tipAnchorRef, placeAnchor],
|
|
984
1376
|
)
|
|
@@ -988,6 +1380,7 @@ export const SelectionOverlay = ({
|
|
|
988
1380
|
* viewport metrics come from cached numbers only.
|
|
989
1381
|
* 唯一の再描画関数 (両経路共有)。内部で gBCR を呼ばない (キャッシュ数値のみ)。
|
|
990
1382
|
*/
|
|
1383
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: 最熱の単一書込漏斗 (§6.7.2-3) — v0.9 の 16 象限一般化 + fill 同値抑制 (census #23) が意図的に本体を広げた。象限毎ヘルパへの分割は identity 安定な ref-bundle 生活構造と単一パス規律 (v0.9 §0.6-1) を壊すため、平坦な 1 関数を契約として保つ
|
|
991
1384
|
const repaintOverlay = useCallback(() => {
|
|
992
1385
|
const overlay = overlayRef.current
|
|
993
1386
|
const handle = handleRef.current
|
|
@@ -1001,13 +1394,14 @@ export const SelectionOverlay = ({
|
|
|
1001
1394
|
// 命令読み (props docstring 参照) — シート切替コミットでも常にリセット済みの最新確定選択
|
|
1002
1395
|
const state = getSelection()
|
|
1003
1396
|
|
|
1004
|
-
// 強調帯の対象レンジ (能力なし / 全 off は空 — 確保もしない)
|
|
1005
|
-
const highlight = activeHighlight()
|
|
1006
|
-
const bandRanges = highlight === null ? NO_RANGES : state.ranges.slice(-BAND_RANGE_CAP)
|
|
1007
|
-
|
|
1008
1397
|
// 確定塗り (fills) — 層ごとの常設プール (ref 配列) を使い回す (毎 repaint の DOM 生成 /
|
|
1009
1398
|
// クエリを避ける)。F = 0 は主プール単層で従来恒等
|
|
1010
1399
|
const layers = layersFor(hx, vy)
|
|
1400
|
+
|
|
1401
|
+
// 強調帯の対象レンジ (能力なし / 全 off は空 — 確保もしない)。上限は象限数キーの実効 cap
|
|
1402
|
+
// (ADR-22 デレート — 4 象限以下は BAND_RANGE_CAP のまま、v0.9 §5.9 手順 3)
|
|
1403
|
+
const highlight = activeHighlight()
|
|
1404
|
+
const bandRanges = highlight === null ? NO_RANGES : state.ranges.slice(-bandRangeCapFor(layers.length))
|
|
1011
1405
|
for (const ctx of layers) {
|
|
1012
1406
|
const container = ctx.nodes.container
|
|
1013
1407
|
if (container === null) {
|
|
@@ -1015,32 +1409,55 @@ export const SelectionOverlay = ({
|
|
|
1015
1409
|
}
|
|
1016
1410
|
const fills = ctx.nodes.fillPool
|
|
1017
1411
|
for (let i = 0; i < Math.max(fills.length, state.ranges.length); i += 1) {
|
|
1018
|
-
let
|
|
1019
|
-
if (
|
|
1020
|
-
node = document.createElement("div")
|
|
1412
|
+
let slot = fills[i]
|
|
1413
|
+
if (slot === undefined) {
|
|
1414
|
+
const node = document.createElement("div")
|
|
1021
1415
|
node.className = "aqsp-selection-fill"
|
|
1022
1416
|
node.setAttribute("data-aqsp-fill", "")
|
|
1023
|
-
//
|
|
1024
|
-
|
|
1025
|
-
//
|
|
1026
|
-
|
|
1027
|
-
|
|
1417
|
+
// 生成時のみ display を初期化 — 以後は同値キャッシュが管理 (v0.9 §2.5 census #23)
|
|
1418
|
+
node.style.display = "none"
|
|
1419
|
+
// 静的クローム床 (分割線先頭 or アクティブ枠 — chromeFloorOf) の手前へ挿入
|
|
1420
|
+
// (v0.9 §2.5 census #24: 分割線マウント後に生成された fill が分割線の上へ来る
|
|
1421
|
+
// 継ぎ目吸収の破れを塞ぐ)。append だと DOM 順でシンクより上に描かれ、Phase 3 の
|
|
1422
|
+
// セル面積エディタが半透明 fill の下に沈む (従来規律は不変)
|
|
1423
|
+
container.insertBefore(node, chromeFloorOf(ctx.nodes))
|
|
1424
|
+
slot = { node, last: { shown: false, width: -1, height: -1, transform: "" } }
|
|
1425
|
+
fills.push(slot)
|
|
1028
1426
|
}
|
|
1029
1427
|
const range = state.ranges[i]
|
|
1030
1428
|
const rect = range === undefined ? null : rangeToLayerRect(range, ctx)
|
|
1429
|
+
// fill 書込規律 (v0.9 §2.5 census #23 — placeBandSegments の同値キャッシュのミラー):
|
|
1430
|
+
// display / width / height は変化時だけ書き、transform も同値キャッシュ (§5.9 手順 3
|
|
1431
|
+
// 第 1 段リトリート — 純横 / 純縦スクロールで動かない象限の byte 恒等文字列は再書込しない)
|
|
1031
1432
|
if (rect === null) {
|
|
1032
|
-
|
|
1433
|
+
if (slot.last.shown) {
|
|
1434
|
+
slot.last.shown = false
|
|
1435
|
+
slot.node.style.display = "none"
|
|
1436
|
+
}
|
|
1033
1437
|
} else {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1438
|
+
if (!slot.last.shown) {
|
|
1439
|
+
slot.last.shown = true
|
|
1440
|
+
slot.node.style.display = "block"
|
|
1441
|
+
}
|
|
1442
|
+
const transform = `translate(${rect.left}px, ${rect.top}px)`
|
|
1443
|
+
if (transform !== slot.last.transform) {
|
|
1444
|
+
slot.last.transform = transform
|
|
1445
|
+
slot.node.style.transform = transform
|
|
1446
|
+
}
|
|
1447
|
+
if (rect.width !== slot.last.width) {
|
|
1448
|
+
slot.last.width = rect.width
|
|
1449
|
+
slot.node.style.width = `${rect.width}px`
|
|
1450
|
+
}
|
|
1451
|
+
if (rect.height !== slot.last.height) {
|
|
1452
|
+
slot.last.height = rect.height
|
|
1453
|
+
slot.node.style.height = `${rect.height}px`
|
|
1454
|
+
}
|
|
1038
1455
|
}
|
|
1039
1456
|
}
|
|
1040
1457
|
// プールを生存レンジ数へ収縮 (多レンジ選択の残骸ノードを毎 repaint 走査し続けない —
|
|
1041
1458
|
// 最熱ループは常に live 選択規模に比例させる)
|
|
1042
1459
|
while (fills.length > state.ranges.length) {
|
|
1043
|
-
fills.pop()?.remove()
|
|
1460
|
+
fills.pop()?.node.remove()
|
|
1044
1461
|
}
|
|
1045
1462
|
|
|
1046
1463
|
// アクティブセル枠 (常設ノードは ref 直読み — dragRect と同じ規律)
|
|
@@ -1061,7 +1478,27 @@ export const SelectionOverlay = ({
|
|
|
1061
1478
|
if (marquee === null) {
|
|
1062
1479
|
continue
|
|
1063
1480
|
}
|
|
1064
|
-
const
|
|
1481
|
+
const mapped = clip === null ? null : rangeToLayerRect(clip.range, ctx)
|
|
1482
|
+
// 縁所有権 (セル罫線の「共有線は隣接 2 セルの一方だけが塗る」畳込みのペイン継ぎ目版)。
|
|
1483
|
+
// 導出: 帯クリップは軸の互いに素な半開区間分割 f [0, W_F) / s [W_F, extent − vis) /
|
|
1484
|
+
// t [extent − vis, extent) — 継ぎ目上のレンジ縁は、レンジの隣接セル列を区間に含む象限
|
|
1485
|
+
// ちょうど 1 つが所有する (区間分割ゆえ所有者は一意で、描画順位 m < s < t < f の
|
|
1486
|
+
// タイブレークは不要 — 順位は重なる画素の上下、所有権は区間の帰属)。非所有側の矩形は
|
|
1487
|
+
// 継ぎ目側でクリップ区間と交差ゼロ — 本体はクリップが切るが、漸進アウトセットは全余白を
|
|
1488
|
+
// 得て ::after のアンツ帯を CLIP_MARQUEE_OUTSET px までクリップ内へ引き戻すため、縁が
|
|
1489
|
+
// 争奪帯 [clamp, clamp + 3) / (−3, 0] にある間だけ継ぎ目の両側に破線が二重化する — t
|
|
1490
|
+
// 写像はスクロール非依存なので (縁が恒常的に 0)、s ペインのスクロール中も幽霊縁が
|
|
1491
|
+
// 継ぎ目に貼り付く。抑止は争奪帯ちょうどに限定: 帯の外の非所有クローンは元よりクリップが
|
|
1492
|
+
// 不可視化しており、書込パターンは v0.8 / T = 0 と byte 恒等のまま (継ぎ目のない m 軸と
|
|
1493
|
+
// viewport 端はフラグ false で常に恒等)
|
|
1494
|
+
const rect =
|
|
1495
|
+
mapped !== null &&
|
|
1496
|
+
((ctx.xSeamTrail && mapped.left >= ctx.clampWidth && mapped.left < ctx.clampWidth + CLIP_MARQUEE_OUTSET) ||
|
|
1497
|
+
(ctx.xSeamLead && mapped.left + mapped.width <= 0 && mapped.left + mapped.width > -CLIP_MARQUEE_OUTSET) ||
|
|
1498
|
+
(ctx.ySeamTrail && mapped.top >= ctx.clampHeight && mapped.top < ctx.clampHeight + CLIP_MARQUEE_OUTSET) ||
|
|
1499
|
+
(ctx.ySeamLead && mapped.top + mapped.height <= 0 && mapped.top + mapped.height > -CLIP_MARQUEE_OUTSET))
|
|
1500
|
+
? null
|
|
1501
|
+
: mapped
|
|
1065
1502
|
if (rect === null) {
|
|
1066
1503
|
marquee.style.display = "none"
|
|
1067
1504
|
} else {
|
|
@@ -1134,18 +1571,31 @@ export const SelectionOverlay = ({
|
|
|
1134
1571
|
if (!eligible || viewportH <= 0 || bodyWidth <= 0) {
|
|
1135
1572
|
fillHandle.style.display = "none"
|
|
1136
1573
|
} else {
|
|
1137
|
-
// 行 / 列とも凍結パーティション順写像 (v0.6 / v0.5 計画 §2)
|
|
1138
|
-
//
|
|
1139
|
-
// 0 が下限)
|
|
1574
|
+
// 行 / 列とも凍結パーティション順写像 (v0.6 / v0.5 計画 §2、v0.9 で 3 分岐)。
|
|
1575
|
+
// スクロール軸の隅が凍結帯の下へ潜った場合は不可視 (minLeft = W_F / minTop = H_F —
|
|
1576
|
+
// 凍結軸自身の隅は 0 が下限)
|
|
1140
1577
|
const frozen = handle.getFrozenSize()
|
|
1578
|
+
// 判断時 forward ctx (v0.9 §2.1 — census #16): 同ブロック既読の getViewportSize()
|
|
1579
|
+
// を extent に、1 スナップショットから両軸をその場インライン構築 (§5.3)
|
|
1580
|
+
const fillCtxX: AxisForwardCtx = { scroll: hx, frozenCount: frozen.cols, trailingCount: frozen.trailingCols, extent: bodyWidth }
|
|
1581
|
+
const fillCtxY: AxisForwardCtx = { scroll: vy, frozenCount: frozen.rows, trailingCount: frozen.trailingRows, extent: viewportH }
|
|
1141
1582
|
const endColIsFrozen = tail.endCol < frozen.cols
|
|
1142
1583
|
const endRowIsFrozen = tail.endRow < frozen.rows
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
const
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1584
|
+
// 厳密分類 (v0.9 §2.7 — FZ21 ミラー): endCol >= count − T が末尾 (境界列 count − T − 1
|
|
1585
|
+
// は SCROLL)。T = 0 は count − 0 = count で恒偽 (v0.8 恒等)
|
|
1586
|
+
const endColIsTrailing = tail.endCol >= colAxis.count - frozen.trailingCols
|
|
1587
|
+
const endRowIsTrailing = tail.endRow >= rowAxis.count - frozen.trailingRows
|
|
1588
|
+
const left = colToViewportX(colAxis, tail.endCol, fillCtxX) + colAxis.sizeOf(tail.endCol)
|
|
1589
|
+
const top = rowToViewportY(rowAxis, tail.endRow, fillCtxY) + rowAxis.sizeOf(tail.endRow)
|
|
1590
|
+
// 床: 凍結隅と末尾隅は 0 (帯内が正位置 — v0.9 §4.10 #16)、スクロール隅は W_F / H_F
|
|
1591
|
+
const minLeft = endColIsFrozen || endColIsTrailing ? 0 : frozen.width
|
|
1592
|
+
const minTop = endRowIsFrozen || endRowIsTrailing ? 0 : frozen.height
|
|
1593
|
+
// 天井 (v0.9 §4.10 #16 — 床の遠双子): スクロール隅が末尾帯 [body − vis, body) へ潜れば
|
|
1594
|
+
// 消灯。末尾隅の天井は body (帯内右端まで可視)。T = 0 は vis 0 で v0.8 の body 天井と一致
|
|
1595
|
+
const maxLeft = endColIsTrailing ? bodyWidth : bodyWidth - frozen.trailingVisibleWidth
|
|
1596
|
+
const maxTop = endRowIsTrailing ? viewportH : viewportH - frozen.trailingVisibleHeight
|
|
1597
|
+
if (top <= minTop || top > maxTop || left <= minLeft || left > maxLeft) {
|
|
1598
|
+
fillHandle.style.display = "none" // 右下隅が viewport 外 / 凍結帯 / 末尾帯の下 (§8.1)
|
|
1149
1599
|
} else {
|
|
1150
1600
|
fillHandle.style.display = "block"
|
|
1151
1601
|
fillHandle.style.transform = `translate(${left - FILL_HANDLE_SIZE + 1}px, ${top - FILL_HANDLE_SIZE + 1}px)`
|
|
@@ -1159,10 +1609,15 @@ export const SelectionOverlay = ({
|
|
|
1159
1609
|
if (sink !== null) {
|
|
1160
1610
|
const a = state.active
|
|
1161
1611
|
const { width: bodyWidth, height: viewportH } = handle.getViewportSize()
|
|
1162
|
-
// 行 / 列とも凍結パーティション順写像 (v0.6 / v0.5 計画 §2 — 凍結軸の active
|
|
1612
|
+
// 行 / 列とも凍結パーティション順写像 (v0.6 / v0.5 計画 §2 — 凍結軸の active は静的座標、
|
|
1613
|
+
// v0.9 で末尾も extent アンカーの静的座標)
|
|
1163
1614
|
const sinkFrozen = handle.getFrozenSize()
|
|
1164
|
-
|
|
1165
|
-
|
|
1615
|
+
// 判断時 forward ctx (v0.9 §2.1 — census #17): 同ブロック既読の getViewportSize() を
|
|
1616
|
+
// extent に、1 スナップショットから両軸をその場インライン構築 (§5.3)
|
|
1617
|
+
const sinkCtxX: AxisForwardCtx = { scroll: hx, frozenCount: sinkFrozen.cols, trailingCount: sinkFrozen.trailingCols, extent: bodyWidth }
|
|
1618
|
+
const sinkCtxY: AxisForwardCtx = { scroll: vy, frozenCount: sinkFrozen.rows, trailingCount: sinkFrozen.trailingRows, extent: viewportH }
|
|
1619
|
+
const rawTop = rowToViewportY(rowAxis, a.row, sinkCtxY)
|
|
1620
|
+
const rawLeft = colToViewportX(colAxis, a.col, sinkCtxX)
|
|
1166
1621
|
const top = viewportH > 0 ? Math.min(Math.max(rawTop, 0), Math.max(0, viewportH - 1)) : 0
|
|
1167
1622
|
const left = bodyWidth > 0 ? Math.min(Math.max(rawLeft, 0), Math.max(0, bodyWidth - 1)) : 0
|
|
1168
1623
|
sink.style.top = `${top}px`
|
|
@@ -1190,7 +1645,13 @@ export const SelectionOverlay = ({
|
|
|
1190
1645
|
// スクロール軸の active が凍結帯の下へ潜った場合もエディタを降ろす (両軸)
|
|
1191
1646
|
const underFrozenCol = a.col >= sinkFrozen.cols && rawLeft < sinkFrozen.width
|
|
1192
1647
|
const underFrozenRow = a.row >= sinkFrozen.rows && rawTop < sinkFrozen.height
|
|
1193
|
-
|
|
1648
|
+
// 末尾帯への侵入でもエディタを降ろす遠双子 (v0.9 §4.10 #17 — `rawLeft < W_F` の正確な
|
|
1649
|
+
// ミラー: 遠端 rawLeft + 幅 > 帯始端 body − vis)。第 1 結合子 col < count − T は
|
|
1650
|
+
// load-bearing の除外 — 末尾セル自身は帯内が正位置で、落とすと末尾セル編集が全て
|
|
1651
|
+
// 不可視シンクへの IME 合成になる (§5.4 陽性ゲート / TF-7)。T = 0 は恒偽 (v0.8 恒等)
|
|
1652
|
+
const underTrailingCol = sinkFrozen.trailingWidth > 0 && a.col < colAxis.count - sinkFrozen.trailingCols && rawLeft + colAxis.sizeOf(a.col) > bodyWidth - sinkFrozen.trailingVisibleWidth
|
|
1653
|
+
const underTrailingRow = sinkFrozen.trailingHeight > 0 && a.row < rowAxis.count - sinkFrozen.trailingRows && rawTop + rowAxis.sizeOf(a.row) > viewportH - sinkFrozen.trailingVisibleHeight
|
|
1654
|
+
const editorVisible = editingRef.current && rawTop === top && rawLeft === left && !underFrozenCol && !underFrozenRow && !underTrailingCol && !underTrailingRow
|
|
1194
1655
|
if (editorVisible !== lastEditingAttrRef.current) {
|
|
1195
1656
|
lastEditingAttrRef.current = editorVisible
|
|
1196
1657
|
if (editorVisible) {
|
|
@@ -1354,7 +1815,7 @@ export const SelectionOverlay = ({
|
|
|
1354
1815
|
// (Spreadsheet の repaintOverlay 呼び) が描く (post-B F2 実測)
|
|
1355
1816
|
useEffect(() => {
|
|
1356
1817
|
repaintOverlay()
|
|
1357
|
-
}, [repaintOverlay, editing, effectiveFrozenCols, effectiveFrozenRows])
|
|
1818
|
+
}, [repaintOverlay, editing, effectiveFrozenCols, effectiveFrozenRows, effectiveTrailingCols, effectiveTrailingRows])
|
|
1358
1819
|
|
|
1359
1820
|
// シンクの mousedown ガード (idle の 1px 直クリックから armed 選択を守る — §4.2 ADR-4。
|
|
1360
1821
|
// 所有は SelectionOverlay — §6.10 の変異検証対象)
|
|
@@ -1368,26 +1829,34 @@ export const SelectionOverlay = ({
|
|
|
1368
1829
|
return () => sink.removeEventListener("mousedown", handler)
|
|
1369
1830
|
}, [sinkRef, onSinkMouseDown])
|
|
1370
1831
|
|
|
1371
|
-
// ---- 象限 JSX の構築 (v0.6 計画 §3-4):
|
|
1372
|
-
// DOM 順は「後置 =
|
|
1373
|
-
// v0.5 互換の data-attr フック (main / frozen / scroll)
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1832
|
+
// ---- 象限 JSX の構築 (v0.6 計画 §3-4、v0.9 §2.5 で 3 分割へ一般化): 各軸の帯種 ×
|
|
1833
|
+
// デカルト積で必要な象限だけ描く。DOM 順は「後置 = 境界での描画上位」。
|
|
1834
|
+
// v0.5 互換の data-attr フック (main / frozen / scroll) は mm / fm / sm に据置き ----
|
|
1835
|
+
// x 帯の初期インライン形状 (レンダ面はハンドル読み不可 — 初回マウント ref null): f は
|
|
1836
|
+
// [0, W_F)、s は [W_F, extent − W_T)、t は right アンカーの幅 W_T (いずれも木 px —
|
|
1837
|
+
// 導出: frozenWidthOf / trailingWidthOf の二値系 §2.2)。viewport クリップ時 (overlap) の
|
|
1838
|
+
// vis への補正は書込パス heal (layersFor) が最初のフレームで行う
|
|
1839
|
+
const xKinds: { k: AxisKind; style: { left?: number; right?: number; width?: number } }[] =
|
|
1840
|
+
effectiveFrozenCols > 0 || effectiveTrailingCols > 0
|
|
1841
|
+
? [
|
|
1842
|
+
...(effectiveFrozenCols > 0 ? [{ k: "f" as const, style: { left: 0, width: overlayFrozenWidth } }] : []),
|
|
1843
|
+
{ k: "s" as const, style: { left: overlayFrozenWidth, right: overlayTrailingWidth } },
|
|
1844
|
+
...(effectiveTrailingCols > 0 ? [{ k: "t" as const, style: { right: 0, width: overlayTrailingWidth } }] : []),
|
|
1845
|
+
]
|
|
1846
|
+
: [{ k: "m" as const, style: { left: 0, right: 0 } }]
|
|
1847
|
+
const yKinds: { k: AxisKind; style: { top?: number; bottom?: number; height?: number } }[] =
|
|
1848
|
+
effectiveFrozenRows > 0 || effectiveTrailingRows > 0
|
|
1849
|
+
? [
|
|
1850
|
+
...(effectiveFrozenRows > 0 ? [{ k: "f" as const, style: { top: 0, height: overlayFrozenHeight } }] : []),
|
|
1851
|
+
{ k: "s" as const, style: { top: overlayFrozenHeight, bottom: overlayTrailingHeight } },
|
|
1852
|
+
...(effectiveTrailingRows > 0 ? [{ k: "t" as const, style: { bottom: 0, height: overlayTrailingHeight } }] : []),
|
|
1853
|
+
]
|
|
1854
|
+
: [{ k: "m" as const, style: { top: 0, bottom: 0 } }]
|
|
1855
|
+
// 描画順位 (v0.9 §2.5 — ADR-22): 主 m0 < スクロール s1 < 末尾 t2 < 凍結 f3 (末尾は scroll の
|
|
1856
|
+
// 上・凍結の下 — コンテナ overlap の leading 優先)。sort は安定 — 同順位は構築順 (f → s → t)
|
|
1857
|
+
// で、v0.8 の 4 象限順 (ss < fs < sf < ff 等) と byte 一致する
|
|
1858
|
+
const rank = (k: string): number => (k === "m" ? 0 : k === "s" ? 1 : k === "t" ? 2 : 3)
|
|
1859
|
+
const quadrants = xKinds.flatMap((x) => yKinds.map((y) => ({ xk: x.k, yk: y.k, key: `${x.k}${y.k}`, style: { ...x.style, ...y.style } }))).sort((a, b) => rank(a.key[0]) + rank(a.key[1]) - (rank(b.key[0]) + rank(b.key[1])))
|
|
1391
1860
|
// R = 0 の 3 象限は v0.5 の data-attr を温存 (E2E / 単体フック互換)。行凍結が入る象限は
|
|
1392
1861
|
// 汎用 data-aqsp-overlay-quad のみ
|
|
1393
1862
|
const legacyAttr = (key: string): Record<string, string> => (key === "mm" ? { "data-aqsp-overlay-main": "" } : key === "fm" ? { "data-aqsp-overlay-frozen": "" } : key === "sm" ? { "data-aqsp-overlay-scroll": "" } : {})
|
|
@@ -1397,10 +1866,24 @@ export const SelectionOverlay = ({
|
|
|
1397
1866
|
// 1 本 §6.7.2-1)。下は v0.4 で追加: 横バー帯がグリッド内在化し、オーバーレイはセル
|
|
1398
1867
|
// ビューポート (バー帯を除く) だけを覆う
|
|
1399
1868
|
<div ref={overlayRef} className="aqsp-overlay" data-aqsp-overlay="" style={{ right: SCROLLBAR_WIDTH, bottom: SCROLLBAR_WIDTH }}>
|
|
1400
|
-
{quadrants.map(({ key, style }) => {
|
|
1869
|
+
{quadrants.map(({ xk, yk, key, style }) => {
|
|
1401
1870
|
const refs = quadRefs[key]
|
|
1402
1871
|
return (
|
|
1403
1872
|
<div key={key} className="aqsp-overlay-quad" data-aqsp-overlay-quad={key} {...legacyAttr(key)} style={style} ref={refs.container}>
|
|
1873
|
+
{/* 分割線セグメント (v0.9 §2.4 — ADR-24 挿入のみ): scroll 種象限の子に限定
|
|
1874
|
+
(構造強制 — 中央帯外に分割線画素が存在できず、overlap 縮退では象限クリップ
|
|
1875
|
+
ごと消える)。位置は activeFrame の直前 = 実行時挿入の fill / 帯の後 ⇒
|
|
1876
|
+
帯 < fill < 分割線 < activeFrame < … が構築だけで立つ。存在は実効件数
|
|
1877
|
+
(render)、可視は layersFor の等値抑制 display heal (帯 px)、静的幾何は
|
|
1878
|
+
生成時のみ — 以後この 4 ノードへの書込は display フリップだけ。
|
|
1879
|
+
静的幾何の導出 (§2.4 の表): x-leading は s 象限原点 = W_F に接する縦線
|
|
1880
|
+
(left: 0)、x-trailing は中央帯右端 = extent − W_T_vis (right: 0)、y は転置。
|
|
1881
|
+
太さは CSS 変数 (--aqsp-divider-width) — 分割線は常に SCROLL 帯の縁画素を
|
|
1882
|
+
覆い凍結帯セルを覆わない (Sheets 見え) */}
|
|
1883
|
+
{xk === "s" && effectiveFrozenCols > 0 ? <div className="aqsp-freeze-divider aqsp-freeze-divider-v" data-aqsp-freeze-divider="x-leading" style={{ left: 0, top: 0, bottom: 0, display: "none" }} ref={refs.dividerXLeading} /> : null}
|
|
1884
|
+
{xk === "s" && effectiveTrailingCols > 0 ? <div className="aqsp-freeze-divider aqsp-freeze-divider-v" data-aqsp-freeze-divider="x-trailing" style={{ right: 0, top: 0, bottom: 0, display: "none" }} ref={refs.dividerXTrailing} /> : null}
|
|
1885
|
+
{yk === "s" && effectiveFrozenRows > 0 ? <div className="aqsp-freeze-divider aqsp-freeze-divider-h" data-aqsp-freeze-divider="y-leading" style={{ top: 0, left: 0, right: 0, display: "none" }} ref={refs.dividerYLeading} /> : null}
|
|
1886
|
+
{yk === "s" && effectiveTrailingRows > 0 ? <div className="aqsp-freeze-divider aqsp-freeze-divider-h" data-aqsp-freeze-divider="y-trailing" style={{ bottom: 0, left: 0, right: 0, display: "none" }} ref={refs.dividerYTrailing} /> : null}
|
|
1404
1887
|
<div className="aqsp-active-frame" data-aqsp-active="" style={{ display: "none" }} ref={refs.active} />
|
|
1405
1888
|
<div className="aqsp-drag-rect" data-aqsp-drag-rect="" style={{ display: "none" }} ref={refs.dragRect} />
|
|
1406
1889
|
<div className="aqsp-clip-marquee" data-aqsp-clip-marquee="" style={{ display: "none" }} ref={refs.marquee} />
|