@aiquants/spreadsheet 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1103 -0
- package/LICENSE.md +155 -0
- package/README.md +817 -0
- package/dist/index.cjs +21 -0
- package/dist/index.d.cts +1960 -0
- package/dist/index.d.ts +1960 -0
- package/dist/index.js +7711 -0
- package/dist/styles/spreadsheet.css +2 -0
- package/dist/styles/spreadsheet.standalone.css +3 -0
- package/package.json +94 -0
- package/src/Spreadsheet.tsx +2313 -0
- package/src/clipboard/clipPayload.ts +1242 -0
- package/src/clipboard/fixtures/excel.fixture.ts +301 -0
- package/src/clipboard/fixtures/html.fixture.ts +90 -0
- package/src/clipboard/fixtures/sheets.fixture.ts +83 -0
- package/src/clipboard/htmlInterop.ts +1694 -0
- package/src/clipboard/numberFormatCodec.ts +870 -0
- package/src/clipboard/pasteSpecial.ts +608 -0
- package/src/clipboard/tiling.ts +370 -0
- package/src/clipboard/tsv.ts +186 -0
- package/src/clipboard/useClipboard.ts +1154 -0
- package/src/commands/selfCalc.ts +279 -0
- package/src/commands/useCommands.ts +946 -0
- package/src/editor/useCellEditor.ts +708 -0
- package/src/grid/CellView.tsx +51 -0
- package/src/grid/GridBody.tsx +489 -0
- package/src/header/ColumnHeader.tsx +167 -0
- package/src/header/RowHeader.tsx +144 -0
- package/src/header/columnName.ts +31 -0
- package/src/header/resizeGates.ts +146 -0
- package/src/header/resizeStrips.ts +79 -0
- package/src/header/useHeaderResize.ts +598 -0
- package/src/header/useHeaderSelection.ts +454 -0
- package/src/index.ts +122 -0
- package/src/keyboard/compositionGuard.ts +53 -0
- package/src/keyboard/ensureCellVisible.ts +77 -0
- package/src/keyboard/shortcuts.ts +718 -0
- package/src/keyboard/useGridKeyboard.ts +940 -0
- package/src/model/axisModel.ts +186 -0
- package/src/model/cellAnnotations.ts +92 -0
- package/src/model/cellCoord.ts +114 -0
- package/src/model/cellFormat.ts +562 -0
- package/src/model/cellPaint.ts +537 -0
- package/src/model/conditionalFormat.ts +805 -0
- package/src/model/dataAdapter.ts +478 -0
- package/src/model/dataValidation.ts +542 -0
- package/src/model/formulaRefs.ts +984 -0
- package/src/model/frozenPartition.ts +100 -0
- package/src/model/labels.ts +228 -0
- package/src/model/viewStore.ts +124 -0
- package/src/overlay/SelectionOverlay.tsx +1425 -0
- package/src/overlay/useFillHandle.ts +432 -0
- package/src/popover/CommentPopover.tsx +109 -0
- package/src/popover/LinkPopover.tsx +108 -0
- package/src/popover/ListPopover.tsx +161 -0
- package/src/popover/MessagePopover.tsx +36 -0
- package/src/popover/NotePopover.tsx +106 -0
- package/src/popover/NoteTip.tsx +26 -0
- package/src/popover/PopoverLayer.tsx +210 -0
- package/src/popover/placement.ts +138 -0
- package/src/popover/usePopovers.ts +642 -0
- package/src/selection/selectionState.ts +239 -0
- package/src/selection/useAutoScroller.ts +226 -0
- package/src/selection/usePointDrag.ts +273 -0
- package/src/selection/useSelectionEngine.ts +687 -0
- package/src/styles/components.entry.css +10 -0
- package/src/styles/spreadsheet.css +1029 -0
- package/src/styles/standalone.entry.css +9 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure popover geometry (v0.8 §2.5 / §4.1.15): `popoverWidth` is the single source
|
|
3
|
+
* of the frame width (min(POPOVER_MAX_WIDTH, usable − margin) — the layer writes it BEFORE
|
|
4
|
+
* measuring the frame, because the wrapped height depends on it); `placePopover` picks the
|
|
5
|
+
* first of four candidate positions (below-right → below-left → above-right → above-left) that
|
|
6
|
+
* fits the body minus the scrollbar strip, otherwise SHRINKS the height to the larger free space
|
|
7
|
+
* beside the anchor (below or above — the kind roots scroll the rest) and pins the popover to
|
|
8
|
+
* the top-left margin only when even that is below `POPOVER_MIN_HEIGHT` or the width does not
|
|
9
|
+
* fit; `shouldShowNoteTip` is the single show predicate of the hover note tip (cell change AND
|
|
10
|
+
* the next cell has a note AND no drag — reference drags included — is in flight). React / DOM
|
|
11
|
+
* free; every branch is pinned by placement.spec.ts.
|
|
12
|
+
*
|
|
13
|
+
* ポップオーバー幾何の純関数 (v0.8 §2.5 / §4.1.15)。popoverWidth はフレーム幅の唯一の源
|
|
14
|
+
* (min(POPOVER_MAX_WIDTH, 使用可能幅 − マージン) — 折り返し後の高さは幅に依存するため、層は
|
|
15
|
+
* 計測の**前**にこれを書く)。placePopover は 4 候補 (below-right → below-left → above-right →
|
|
16
|
+
* above-left) のうち body − スクロールバー帯に最初に収まるものを選び、どれも収まらなければ
|
|
17
|
+
* アンカー脇の広い方 (下 / 上) の空きへ高さを**縮めて**置き (残りは kind ルートがスクロール)、
|
|
18
|
+
* それすら POPOVER_MIN_HEIGHT を割る / 幅が収まらないときだけ左上マージンへピン留め。
|
|
19
|
+
* shouldShowNoteTip はホバー note tip の唯一の表示述語 (セル変化 ∧ 次セルにノートあり ∧ いかなる
|
|
20
|
+
* ドラッグ (参照ドラッグ含む) も飛行中でない)。React / DOM 非依存で、全分岐を placement.spec.ts が固定。
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { type CellCoord, coordEquals } from "../model/cellCoord.ts"
|
|
24
|
+
import { SCROLLBAR_WIDTH } from "../model/viewStore.ts"
|
|
25
|
+
|
|
26
|
+
/** A rectangle in the popover layer's coordinate space (logical viewport px). / ポップオーバー層座標系 (論理 viewport px) の矩形。 */
|
|
27
|
+
export type PopoverRect = { top: number; left: number; width: number; height: number }
|
|
28
|
+
|
|
29
|
+
/** The body (layer) size placement works in. / 配置が扱う body (層) の寸法。 */
|
|
30
|
+
export type PopoverBodySize = { width: number; height: number }
|
|
31
|
+
|
|
32
|
+
/** Maximum popover width in logical px. / ポップオーバーの最大幅 (論理 px)。 */
|
|
33
|
+
export const POPOVER_MAX_WIDTH = 320
|
|
34
|
+
|
|
35
|
+
/** Margin subtracted from the usable body and used as the pinned top-left offset. / 使用可能領域から差し引くマージン兼ピン留め時の左上オフセット。 */
|
|
36
|
+
export const POPOVER_MARGIN = 8
|
|
37
|
+
|
|
38
|
+
/** The smallest height a popover shrunk beside its anchor keeps (about two tip lines); below it the popover is pinned instead. / アンカー脇へ縮めたポップオーバーが保つ最小高 (tip 約 2 行)。これを割るならピン留め。 */
|
|
39
|
+
export const POPOVER_MIN_HEIGHT = 64
|
|
40
|
+
|
|
41
|
+
/** Grace after the pointer leaves the tip's cell before the tip hides (hoverable tip). / ポインタが tip のセルを離れてから消灯するまでの猶予 (tip はホバー可)。 */
|
|
42
|
+
export const NOTE_TIP_LEAVE_GRACE_MS = 150
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The popover width for a body: min(POPOVER_MAX_WIDTH, usable width − margin), never negative,
|
|
46
|
+
* where the usable width is the body minus the scrollbar strip. It depends on the body only, so
|
|
47
|
+
* the layer applies it to the frame BEFORE reading the frame's height (a narrower frame wraps
|
|
48
|
+
* more lines — measuring at another width under-estimates the height).
|
|
49
|
+
* body に対するポップオーバー幅: min(POPOVER_MAX_WIDTH, 使用可能幅 − マージン) (負にしない)。
|
|
50
|
+
* 使用可能幅 = body − スクロールバー帯。body だけで決まるため、層はフレームの高さを読む**前**に
|
|
51
|
+
* これを書く (狭いフレームは行数が増える — 別の幅で測ると高さを過小評価する)。
|
|
52
|
+
*/
|
|
53
|
+
export const popoverWidth = (bodyRect: PopoverBodySize): number => Math.max(0, Math.min(POPOVER_MAX_WIDTH, bodyRect.width - SCROLLBAR_WIDTH - POPOVER_MARGIN))
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Places a popover of `measuredHeight` (measured at `popoverWidth(bodyRect)`) next to
|
|
57
|
+
* `anchorRect` inside `bodyRect` (both in layer px). The usable area is the body minus the
|
|
58
|
+
* scrollbar strip on both axes; the height is capped at usable − margin (never negative).
|
|
59
|
+
* Candidates are tried in the fixed order below-right, below-left, above-right, above-left; the
|
|
60
|
+
* first one lying entirely inside the usable area wins. When none fits at full height, the height
|
|
61
|
+
* is shrunk to the larger free space beside the anchor (below the anchor's bottom edge, or above
|
|
62
|
+
* its top edge — below wins a tie) and the popover is placed there with the first horizontal
|
|
63
|
+
* alignment (anchor-left, then anchor-right) that fits, so it never covers the anchor cell; only
|
|
64
|
+
* when that free space is below `POPOVER_MIN_HEIGHT` or no alignment fits horizontally is the
|
|
65
|
+
* popover pinned at (POPOVER_MARGIN, POPOVER_MARGIN).
|
|
66
|
+
* ポップオーバー (高さ measuredHeight — popoverWidth(bodyRect) の幅で計測済み) を anchorRect の隣へ
|
|
67
|
+
* bodyRect 内 (いずれも層 px) に配置。使用可能領域は body から両軸のスクロールバー帯を引いたもの。
|
|
68
|
+
* 高さは 使用可能高 − マージン で上限 (負にはしない)。候補は below-right → below-left →
|
|
69
|
+
* above-right → above-left の固定順で、使用可能領域に丸ごと収まる最初の候補を採用。全高で収まる
|
|
70
|
+
* 候補が無ければ、アンカー脇の広い方の空き (アンカー下辺より下 / 上辺より上 — 同点は下) へ高さを縮め、
|
|
71
|
+
* 横に収まる最初の揃え (アンカー左揃え → 右辺揃え) で置く (アンカーセルを覆わない)。その空きが
|
|
72
|
+
* POPOVER_MIN_HEIGHT を割るか横にどちらも収まらないときだけ (POPOVER_MARGIN, POPOVER_MARGIN) へピン留め。
|
|
73
|
+
*/
|
|
74
|
+
export const placePopover = (anchorRect: PopoverRect, measuredHeight: number, bodyRect: PopoverBodySize): PopoverRect => {
|
|
75
|
+
// 使用可能領域 = body − スクロールバー帯 (両軸)
|
|
76
|
+
const usableWidth = bodyRect.width - SCROLLBAR_WIDTH
|
|
77
|
+
const usableHeight = bodyRect.height - SCROLLBAR_WIDTH
|
|
78
|
+
// 幅は body から (層が計測前に書いた値と同じ源)、高さは実測をマージン込みの使用可能高で上限 (負にしない)
|
|
79
|
+
const width = popoverWidth(bodyRect)
|
|
80
|
+
const height = Math.max(0, Math.min(measuredHeight, usableHeight - POPOVER_MARGIN))
|
|
81
|
+
const anchorRight = anchorRect.left + anchorRect.width
|
|
82
|
+
const anchorBottom = anchorRect.top + anchorRect.height
|
|
83
|
+
// 横の揃えはアンカー左揃え → 右辺揃えの固定順、使用可能幅に丸ごと収まるもの
|
|
84
|
+
const fitsX = (left: number): boolean => left >= 0 && left + width <= usableWidth
|
|
85
|
+
const lefts: readonly number[] = [anchorRect.left, anchorRight - width]
|
|
86
|
+
// 候補順は固定 (below-right → below-left → above-right → above-left)
|
|
87
|
+
const candidates: readonly { left: number; top: number }[] = [
|
|
88
|
+
{ left: anchorRect.left, top: anchorBottom },
|
|
89
|
+
{ left: anchorRight - width, top: anchorBottom },
|
|
90
|
+
{ left: anchorRect.left, top: anchorRect.top - height },
|
|
91
|
+
{ left: anchorRight - width, top: anchorRect.top - height },
|
|
92
|
+
]
|
|
93
|
+
for (const candidate of candidates) {
|
|
94
|
+
// 使用可能領域に丸ごと収まる最初の候補
|
|
95
|
+
if (fitsX(candidate.left) && candidate.top >= 0 && candidate.top + height <= usableHeight) {
|
|
96
|
+
return { left: candidate.left, top: candidate.top, width, height }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// 全高では収まらない ⇒ アンカー脇の広い方 (下 / 上) の空きへ高さを縮める (残りは kind ルートがスクロール)
|
|
100
|
+
const freeBelow = usableHeight - anchorBottom
|
|
101
|
+
const freeAbove = anchorRect.top
|
|
102
|
+
const shrunk = Math.min(height, Math.max(freeBelow, freeAbove))
|
|
103
|
+
if (shrunk >= POPOVER_MIN_HEIGHT) {
|
|
104
|
+
// 同点は下 (候補順と同じ優先)、上なら上辺揃え
|
|
105
|
+
const top = freeBelow >= freeAbove ? anchorBottom : anchorRect.top - shrunk
|
|
106
|
+
for (const left of lefts) {
|
|
107
|
+
if (fitsX(left)) {
|
|
108
|
+
return { left, top, width, height: shrunk }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// 横に収まらない / 縮めても最小高を割る ⇒ 左上マージンへピン留め
|
|
113
|
+
return { left: POPOVER_MARGIN, top: POPOVER_MARGIN, width, height }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The note-tip show predicate: true only when the hovered cell CHANGED to a cell that has a note
|
|
118
|
+
* while no drag (selection / header / resize / fill / reference) is in flight. `hasNote` is
|
|
119
|
+
* consulted only when the other conditions hold (no adapter read on a same-cell move or mid-drag).
|
|
120
|
+
* note tip の表示述語: ホバーセルがノートを持つ別のセルへ**変化**し、かつドラッグ (選択 /
|
|
121
|
+
* ヘッダー / リサイズ / fill / 参照) が 1 つも飛行中でないときだけ true。hasNote は他条件が
|
|
122
|
+
* 成立したときだけ諮る (同一セル内の移動やドラッグ中にアダプタを読まない)。
|
|
123
|
+
*/
|
|
124
|
+
export const shouldShowNoteTip = (prev: CellCoord | null, next: CellCoord | null, isDraggingAny: boolean, hasNote: (cell: CellCoord) => boolean): boolean => {
|
|
125
|
+
// 離脱 (null) は表示しない
|
|
126
|
+
if (next === null) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
// 同一セル内の移動は変化ではない
|
|
130
|
+
if (prev !== null && coordEquals(prev, next)) {
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
// いかなるドラッグ飛行中も表示しない (参照ドラッグ含む)
|
|
134
|
+
if (isDraggingAny) {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
return hasNote(next)
|
|
138
|
+
}
|