@rowsncolumns/spreadsheet 10.6.0 → 10.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/api/cf-data-bar.d.ts +73 -0
- package/dist/cjs/api/cf-data-bar.d.ts.map +1 -0
- package/dist/cjs/api/cf-data-bar.js +182 -0
- package/dist/cjs/api/cf-icons.d.ts +73 -0
- package/dist/cjs/api/cf-icons.d.ts.map +1 -0
- package/dist/cjs/api/cf-icons.js +322 -0
- package/dist/cjs/api/cf-overlays.d.ts +89 -3
- package/dist/cjs/api/cf-overlays.d.ts.map +1 -1
- package/dist/cjs/api/cf-overlays.js +256 -80
- package/dist/cjs/api/cf-priority.d.ts +34 -0
- package/dist/cjs/api/cf-priority.d.ts.map +1 -0
- package/dist/cjs/api/cf-priority.js +94 -0
- package/dist/cjs/api/heatmap.d.ts +7 -1
- package/dist/cjs/api/heatmap.d.ts.map +1 -1
- package/dist/cjs/api/heatmap.js +27 -11
- package/dist/cjs/api/selections.d.ts +2 -2
- package/dist/cjs/api/validation.d.ts +6 -0
- package/dist/cjs/api/validation.d.ts.map +1 -1
- package/dist/cjs/api/validation.js +10 -1
- package/dist/cjs/components/canvas-grid/index.d.ts.map +1 -1
- package/dist/cjs/components/canvas-grid/index.js +194 -51
- package/dist/cjs/components/canvas-grid/overflow.d.ts +37 -0
- package/dist/cjs/components/canvas-grid/overflow.d.ts.map +1 -0
- package/dist/cjs/components/canvas-grid/overflow.js +49 -0
- package/dist/cjs/components/canvas-grid/page-breaks.d.ts +15 -0
- package/dist/cjs/components/canvas-grid/page-breaks.d.ts.map +1 -0
- package/dist/cjs/components/canvas-grid/page-breaks.js +77 -0
- package/dist/cjs/components/canvas-grid/typings.d.ts +25 -0
- package/dist/cjs/components/canvas-grid/typings.d.ts.map +1 -1
- package/dist/cjs/components/cell/alignment.d.ts +83 -1
- package/dist/cjs/components/cell/alignment.d.ts.map +1 -1
- package/dist/cjs/components/cell/alignment.js +182 -1
- package/dist/cjs/components/cell/cell-background.d.ts +18 -0
- package/dist/cjs/components/cell/cell-background.d.ts.map +1 -1
- package/dist/cjs/components/cell/cell-background.js +145 -59
- package/dist/cjs/components/cell/cell-backgrounds.d.ts.map +1 -1
- package/dist/cjs/components/cell/cell-backgrounds.js +5 -1
- package/dist/cjs/components/cell/cell-text-runs.d.ts +12 -5
- package/dist/cjs/components/cell/cell-text-runs.d.ts.map +1 -1
- package/dist/cjs/components/cell/cell-text-runs.js +19 -21
- package/dist/cjs/components/cell/index.d.ts.map +1 -1
- package/dist/cjs/components/cell/index.js +392 -45
- package/dist/cjs/components/cell/number-format-layout.d.ts +35 -0
- package/dist/cjs/components/cell/number-format-layout.d.ts.map +1 -0
- package/dist/cjs/components/cell/number-format-layout.js +106 -0
- package/dist/cjs/components/cell/typings.d.ts +22 -0
- package/dist/cjs/components/cell/typings.d.ts.map +1 -1
- package/dist/cjs/components/cell-editor/index.js +2 -2
- package/dist/cjs/components/cell-filter/index.d.ts.map +1 -1
- package/dist/cjs/components/cell-filter/index.js +3 -3
- package/dist/cjs/components/cell-overlay/index.d.ts +20 -0
- package/dist/cjs/components/cell-overlay/index.d.ts.map +1 -1
- package/dist/cjs/components/cell-overlay/index.js +155 -28
- package/dist/cjs/components/cf-icon-preview/index.d.ts +16 -0
- package/dist/cjs/components/cf-icon-preview/index.d.ts.map +1 -0
- package/dist/cjs/components/cf-icon-preview/index.js +48 -0
- package/dist/cjs/components/embeds/embed-component.d.ts.map +1 -1
- package/dist/cjs/components/embeds/embed-component.js +3 -2
- package/dist/cjs/components/hidden-markers/index.d.ts.map +1 -1
- package/dist/cjs/components/hidden-markers/index.js +8 -7
- package/dist/cjs/components/hidden-markers/style.d.ts +7 -0
- package/dist/cjs/components/hidden-markers/style.d.ts.map +1 -1
- package/dist/cjs/components/hidden-markers/style.js +13 -2
- package/dist/cjs/components/protect-sheet-dialog/index.d.ts +67 -0
- package/dist/cjs/components/protect-sheet-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/protect-sheet-dialog/index.js +196 -0
- package/dist/cjs/components/toolbar/types.d.ts +1 -1
- package/dist/cjs/components/toolbar/types.d.ts.map +1 -1
- package/dist/cjs/config.d.ts +13 -1
- package/dist/cjs/config.d.ts.map +1 -1
- package/dist/cjs/config.js +15 -3
- package/dist/cjs/helpers.d.ts +15 -5
- package/dist/cjs/helpers.d.ts.map +1 -1
- package/dist/cjs/helpers.js +51 -35
- package/dist/cjs/hooks/use-border-styles.d.ts.map +1 -1
- package/dist/cjs/hooks/use-border-styles.js +5 -28
- package/dist/cjs/hooks/use-cell-renderer.d.ts +35 -3
- package/dist/cjs/hooks/use-cell-renderer.d.ts.map +1 -1
- package/dist/cjs/hooks/use-cell-renderer.js +85 -47
- package/dist/cjs/hooks/use-conditional-formatting.d.ts +13 -1
- package/dist/cjs/hooks/use-conditional-formatting.d.ts.map +1 -1
- package/dist/cjs/hooks/use-conditional-formatting.js +281 -230
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +5 -0
- package/dist/cjs/release-date.js +1 -1
- package/dist/cjs/themes.d.ts +8 -0
- package/dist/cjs/themes.d.ts.map +1 -1
- package/dist/cjs/themes.js +21 -3
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/cjs/types.d.ts +81 -4
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/esm/api/cf-data-bar.d.ts +73 -0
- package/dist/esm/api/cf-data-bar.d.ts.map +1 -0
- package/dist/esm/api/cf-data-bar.js +178 -0
- package/dist/esm/api/cf-icons.d.ts +73 -0
- package/dist/esm/api/cf-icons.d.ts.map +1 -0
- package/dist/esm/api/cf-icons.js +306 -0
- package/dist/esm/api/cf-overlays.d.ts +89 -3
- package/dist/esm/api/cf-overlays.d.ts.map +1 -1
- package/dist/esm/api/cf-overlays.js +223 -67
- package/dist/esm/api/cf-priority.d.ts +34 -0
- package/dist/esm/api/cf-priority.d.ts.map +1 -0
- package/dist/esm/api/cf-priority.js +61 -0
- package/dist/esm/api/heatmap.d.ts +7 -1
- package/dist/esm/api/heatmap.d.ts.map +1 -1
- package/dist/esm/api/heatmap.js +24 -9
- package/dist/esm/api/selections.d.ts +2 -2
- package/dist/esm/api/validation.d.ts +6 -0
- package/dist/esm/api/validation.d.ts.map +1 -1
- package/dist/esm/api/validation.js +6 -0
- package/dist/esm/components/canvas-grid/index.d.ts.map +1 -1
- package/dist/esm/components/canvas-grid/index.js +163 -47
- package/dist/esm/components/canvas-grid/overflow.d.ts +37 -0
- package/dist/esm/components/canvas-grid/overflow.d.ts.map +1 -0
- package/dist/esm/components/canvas-grid/overflow.js +39 -0
- package/dist/esm/components/canvas-grid/page-breaks.d.ts +15 -0
- package/dist/esm/components/canvas-grid/page-breaks.d.ts.map +1 -0
- package/dist/esm/components/canvas-grid/page-breaks.js +50 -0
- package/dist/esm/components/canvas-grid/typings.d.ts +25 -0
- package/dist/esm/components/canvas-grid/typings.d.ts.map +1 -1
- package/dist/esm/components/cell/alignment.d.ts +83 -1
- package/dist/esm/components/cell/alignment.d.ts.map +1 -1
- package/dist/esm/components/cell/alignment.js +143 -1
- package/dist/esm/components/cell/cell-background.d.ts +18 -0
- package/dist/esm/components/cell/cell-background.d.ts.map +1 -1
- package/dist/esm/components/cell/cell-background.js +110 -41
- package/dist/esm/components/cell/cell-backgrounds.d.ts.map +1 -1
- package/dist/esm/components/cell/cell-backgrounds.js +5 -1
- package/dist/esm/components/cell/cell-text-runs.d.ts +12 -5
- package/dist/esm/components/cell/cell-text-runs.d.ts.map +1 -1
- package/dist/esm/components/cell/cell-text-runs.js +19 -24
- package/dist/esm/components/cell/index.d.ts.map +1 -1
- package/dist/esm/components/cell/index.js +352 -46
- package/dist/esm/components/cell/number-format-layout.d.ts +35 -0
- package/dist/esm/components/cell/number-format-layout.d.ts.map +1 -0
- package/dist/esm/components/cell/number-format-layout.js +99 -0
- package/dist/esm/components/cell/typings.d.ts +22 -0
- package/dist/esm/components/cell/typings.d.ts.map +1 -1
- package/dist/esm/components/cell-editor/index.js +2 -2
- package/dist/esm/components/cell-filter/index.d.ts.map +1 -1
- package/dist/esm/components/cell-filter/index.js +4 -4
- package/dist/esm/components/cell-overlay/index.d.ts +20 -0
- package/dist/esm/components/cell-overlay/index.d.ts.map +1 -1
- package/dist/esm/components/cell-overlay/index.js +111 -25
- package/dist/esm/components/cf-icon-preview/index.d.ts +16 -0
- package/dist/esm/components/cf-icon-preview/index.d.ts.map +1 -0
- package/dist/esm/components/cf-icon-preview/index.js +41 -0
- package/dist/esm/components/embeds/embed-component.d.ts.map +1 -1
- package/dist/esm/components/embeds/embed-component.js +4 -3
- package/dist/esm/components/hidden-markers/index.d.ts.map +1 -1
- package/dist/esm/components/hidden-markers/index.js +9 -8
- package/dist/esm/components/hidden-markers/style.d.ts +7 -0
- package/dist/esm/components/hidden-markers/style.d.ts.map +1 -1
- package/dist/esm/components/hidden-markers/style.js +8 -1
- package/dist/esm/components/protect-sheet-dialog/index.d.ts +67 -0
- package/dist/esm/components/protect-sheet-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/protect-sheet-dialog/index.js +154 -0
- package/dist/esm/components/toolbar/types.d.ts +1 -1
- package/dist/esm/components/toolbar/types.d.ts.map +1 -1
- package/dist/esm/config.d.ts +13 -1
- package/dist/esm/config.d.ts.map +1 -1
- package/dist/esm/config.js +13 -1
- package/dist/esm/helpers.d.ts +15 -5
- package/dist/esm/helpers.d.ts.map +1 -1
- package/dist/esm/helpers.js +48 -32
- package/dist/esm/hooks/use-border-styles.d.ts.map +1 -1
- package/dist/esm/hooks/use-border-styles.js +5 -26
- package/dist/esm/hooks/use-cell-renderer.d.ts +35 -3
- package/dist/esm/hooks/use-cell-renderer.d.ts.map +1 -1
- package/dist/esm/hooks/use-cell-renderer.js +83 -42
- package/dist/esm/hooks/use-conditional-formatting.d.ts +13 -1
- package/dist/esm/hooks/use-conditional-formatting.d.ts.map +1 -1
- package/dist/esm/hooks/use-conditional-formatting.js +215 -120
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +5 -0
- package/dist/esm/release-date.js +1 -1
- package/dist/esm/themes.d.ts +8 -0
- package/dist/esm/themes.d.ts.map +1 -1
- package/dist/esm/themes.js +20 -3
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/types.d.ts +81 -4
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/spreadsheet.min.css +1 -1
- package/package.json +4 -4
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { CELL_TEXT_PADDING, getTextNodeBounds, Image, } from "@rowsncolumns/grid";
|
|
2
2
|
import { getContrastColor, isNil } from "@rowsncolumns/utils";
|
|
3
3
|
import React, { memo, useCallback } from "react";
|
|
4
|
-
import { Group, Label,
|
|
5
|
-
import {
|
|
4
|
+
import { Group, Label, Shape, Tag, Text } from "react-konva";
|
|
5
|
+
import { paintDataBar } from "../../api/cf-data-bar";
|
|
6
|
+
import { getCfIcon, paintCfIcon } from "../../api/cf-icons";
|
|
7
|
+
import { ARROW_DOWN_WIDTH, CELL_HORIZONTAL_ALIGNMENT, CELL_VERTICAL_ALIGNMENT, CF_ICON_SPACING, CF_ICON_WIDTH, CHIP_HEIGHT, CHIP_RADIUS, CHIP_SPACING, COMMENT_COLOR, DEFAULT_CELL_SPACING, ERROR_COLOR, INDENT_SPACING, INVALID_COLOR, NOTE_COLOR, } from "../../config";
|
|
6
8
|
import { getChipVerticalPosition, normalizeDisplayText, pointToPixel, } from "../../helpers";
|
|
7
|
-
import { getStringifiedColor } from "../../themes";
|
|
9
|
+
import { getStringifiedColor, resolveThemeFontFamily } from "../../themes";
|
|
8
10
|
import { CellArrow } from "../cell-arrow";
|
|
9
11
|
import { CellCalculated } from "../cell-calculated";
|
|
10
12
|
import { CellCheckbox } from "../cell-checkbox";
|
|
@@ -12,18 +14,17 @@ import { CellError, ERROR_TAG_WIDTH } from "../cell-error";
|
|
|
12
14
|
import { CellExpand } from "../cell-expand";
|
|
13
15
|
import { CellFilter } from "../cell-filter";
|
|
14
16
|
import { CellGroup } from "../cell-group";
|
|
17
|
+
import { alignmentImpliesWrap, computeShrinkToFitFontSize, fillCellText, getKonvaRotationDegrees, getRotatedTextLayout, getScriptTextOffsetY, getUnderlineStrokes, getVerticalSpreadLineHeight, layoutDistributedLines, resolveCanvasTextAlign, resolveCanvasVerticalAlign, resolveReadingOrderAlignment, stackTextVertically, verticalAlignmentImpliesWrap, } from "./alignment";
|
|
15
18
|
import { CellTextRuns } from "./cell-text-runs";
|
|
19
|
+
import { getLeftSpacing, getRightSpacing } from "./helpers";
|
|
16
20
|
import { getLineMetrics } from "./line-height";
|
|
21
|
+
import { layoutNumberFormatParts } from "./number-format-layout";
|
|
17
22
|
import { hasRenderableRuns } from "./rich-text";
|
|
18
|
-
import { alignmentImpliesWrap, computeShrinkToFitFontSize, fillCellText, getKonvaRotationDegrees, getRotatedTextLayout, resolveCanvasTextAlign, stackTextVertically, } from "./alignment";
|
|
19
|
-
import { getLeftSpacing, getRightSpacing } from "./helpers";
|
|
20
23
|
const LOADING_VALUE = "Loading...";
|
|
21
24
|
// CSS-equivalent scale for sup/sub baseline offset (matches browser default).
|
|
22
25
|
const BASELINE_OFFSET_FONT_SCALE = 0.58;
|
|
23
|
-
// Vertical inset of data-bar rect from cell edges (top/bottom).
|
|
24
|
-
const CF_DATABAR_INSET_Y = 2;
|
|
25
26
|
export const Cell = memo((props) => {
|
|
26
|
-
const { x, y, width, height, value, padding = CELL_TEXT_PADDING, scale = 1, lineHeight = 1.2, lineHeightMode = "fontMetrics", imageUrl, cellFormat, chipBackgroundColor, defaultColor, defaultBackgroundColor, sheetId, rowIndex, columnIndex, onRequestEdit, onArrowClick, isChecked, isInvalid, isError, showCheckbox, showArrow, showFilter, showChip, showNote, showComment, isFilterActive, filterIconColor, onFilterClick, onCheckboxClick, enableOverflow, cellWidth, textWidth: _textWidth, theme, showCalculatedColumnIcon, isCalculatedColumn, expandable, expanded, columnSpans, overflowRight, showGridLines, structuredValue, sparklineGroupAxis, defaultFontSizePoint, sortOrder, textFormatRuns, StructuredResult, readonly = false, onExpandCollapse, getDefaultHorizontalAlignment, } = props;
|
|
27
|
+
const { x, y, width, height, value, padding = CELL_TEXT_PADDING, scale = 1, lineHeight = 1.2, lineHeightMode = "fontMetrics", imageUrl, cellFormat, chipBackgroundColor, defaultColor, defaultBackgroundColor, sheetId, rowIndex, columnIndex, onRequestEdit, onArrowClick, isChecked, isInvalid, isError, showCheckbox, showArrow, showFilter, showChip, showNote, showComment, isFilterActive, filterIconColor, onFilterClick, onCheckboxClick, enableOverflow, cellWidth, textWidth: _textWidth, theme, showCalculatedColumnIcon, isCalculatedColumn, expandable, expanded, columnSpans, overflowRight, overflowLeft, centerOnSpan = false, arrowOutside = false, showGridLines, structuredValue, sparklineGroupAxis, defaultFontSizePoint, sortOrder, textFormatRuns, numberFormatLayout, StructuredResult, readonly = false, onExpandCollapse, getDefaultHorizontalAlignment, } = props;
|
|
27
28
|
const { textFormat, wrapStrategy } = cellFormat ?? {};
|
|
28
29
|
// openpyxl's `relativeIndent` is an additive delta on top of `indent`.
|
|
29
30
|
const indent = (cellFormat?.indent ?? 0) + (cellFormat?.relativeIndent ?? 0);
|
|
@@ -33,25 +34,37 @@ export const Cell = memo((props) => {
|
|
|
33
34
|
// we cross-package-duplicate the sentinel so apps/spreadsheet doesn't
|
|
34
35
|
// depend on spreadsheet-state.
|
|
35
36
|
const isLoading = value === LOADING_VALUE;
|
|
37
|
+
// Format Cells → Alignment → Text direction: a right-to-left reading
|
|
38
|
+
// order starts General-aligned text at the right edge.
|
|
39
|
+
const readingOrder = cellFormat?.readingOrder;
|
|
36
40
|
const horizontalAlignment = isLoading
|
|
37
41
|
? "center"
|
|
38
42
|
: cellFormat?.horizontalAlignment ??
|
|
39
|
-
(getDefaultHorizontalAlignment(sheetId, rowIndex, columnIndex) ||
|
|
43
|
+
(resolveReadingOrderAlignment(readingOrder, getDefaultHorizontalAlignment(sheetId, rowIndex, columnIndex)) ||
|
|
40
44
|
CELL_HORIZONTAL_ALIGNMENT);
|
|
41
|
-
|
|
45
|
+
// Excel's vertical justify / distributed paint as top / middle for one
|
|
46
|
+
// line and spread wrapped lines over the cell height (below).
|
|
47
|
+
const cellVerticalAlignment = cellFormat?.verticalAlignment || CELL_VERTICAL_ALIGNMENT;
|
|
48
|
+
const verticalAlignment = resolveCanvasVerticalAlign(cellVerticalAlignment);
|
|
49
|
+
const isVerticalSpread = verticalAlignmentImpliesWrap(cellVerticalAlignment);
|
|
42
50
|
// Excel-only modes (justify / distributed / fill / centerContinuous)
|
|
43
51
|
// collapse onto what the canvas text node can paint.
|
|
44
52
|
const canvasTextAlign = resolveCanvasTextAlign(horizontalAlignment);
|
|
45
53
|
const isFillAlignment = horizontalAlignment === "fill";
|
|
46
54
|
// Text format
|
|
47
|
-
const { color: colorObject, bold, italic, strikethrough, underline, baselineOffset, } = textFormat ?? {};
|
|
55
|
+
const { color: colorObject, bold, italic, strikethrough, underline, underlineStyle, baselineOffset, } = textFormat ?? {};
|
|
48
56
|
const fontSizePt = textFormat?.fontSize ?? defaultFontSizePoint;
|
|
49
57
|
// Fall back to the theme's body font so the painter agrees with the
|
|
50
58
|
// sizer (AutoSizerCanvas measures with theme.primaryFontFamily).
|
|
51
|
-
const fontFamily = textFormat?.fontFamily ??
|
|
59
|
+
const fontFamily = resolveThemeFontFamily(textFormat?.fontFamily, theme) ??
|
|
60
|
+
theme?.primaryFontFamily;
|
|
52
61
|
const color = getStringifiedColor(colorObject, theme);
|
|
53
|
-
// Text decoration
|
|
54
|
-
|
|
62
|
+
// Text decoration. Konva draws only a single underline; the double and
|
|
63
|
+
// accounting underlines (Format Cells → Font → Underline) are stroked by
|
|
64
|
+
// `underlineSceneFunc` below, where the text is one unrotated line.
|
|
65
|
+
const hasStyledUnderline = !!underline && !!underlineStyle && underlineStyle !== "single";
|
|
66
|
+
const textDecorationFor = (konvaUnderline) => `${konvaUnderline ? "underline" : ""} ${strikethrough ? "line-through" : ""}`;
|
|
67
|
+
const textDecoration = textDecorationFor(!!underline);
|
|
55
68
|
/* Because of 1px + 0.5px (gridline width + spacing )*/
|
|
56
69
|
const borderWidth = 1;
|
|
57
70
|
const cellSpacingY = borderWidth;
|
|
@@ -59,18 +72,21 @@ export const Cell = memo((props) => {
|
|
|
59
72
|
const fontSize = baselineOffset
|
|
60
73
|
? baseFontSize * BASELINE_OFFSET_FONT_SCALE
|
|
61
74
|
: baseFontSize;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
? "bottom"
|
|
66
|
-
: verticalAlignment;
|
|
75
|
+
// Superscript / subscript keep the cell's alignment and move the
|
|
76
|
+
// baseline instead (`scriptOffsetY` below), as Excel does.
|
|
77
|
+
const effectiveVerticalAlignment = verticalAlignment;
|
|
67
78
|
const textStyle = `${bold ? "bold" : "normal"} ${italic ? "italic" : "normal"}`;
|
|
68
79
|
const showText = !isNil(structuredValue) ? false : !isNil(value);
|
|
69
80
|
// Overflow line
|
|
70
81
|
const overflowFillFunc = useCallback((context, shape) => {
|
|
71
|
-
for (const [
|
|
82
|
+
for (const [spanColumnIndex, columnOffset, columnWidth, cellBackground,] of columnSpans) {
|
|
72
83
|
context.beginPath();
|
|
73
|
-
|
|
84
|
+
// A column left of the cell hides the gridline on its right edge,
|
|
85
|
+
// one to the right the gridline on its left edge.
|
|
86
|
+
const x = columnOffset -
|
|
87
|
+
shape.x() +
|
|
88
|
+
(spanColumnIndex < columnIndex ? columnWidth : 0) +
|
|
89
|
+
0.5;
|
|
74
90
|
const strokeColor = cellBackground
|
|
75
91
|
? getStringifiedColor(cellBackground, theme)
|
|
76
92
|
: defaultBackgroundColor;
|
|
@@ -81,7 +97,7 @@ export const Cell = memo((props) => {
|
|
|
81
97
|
context.stroke();
|
|
82
98
|
context.closePath();
|
|
83
99
|
}
|
|
84
|
-
}, [defaultBackgroundColor, columnSpans, theme,
|
|
100
|
+
}, [defaultBackgroundColor, columnSpans, theme, columnIndex, scale]);
|
|
85
101
|
// Show error tag
|
|
86
102
|
const showError = isError || isInvalid;
|
|
87
103
|
// Calculated
|
|
@@ -89,13 +105,26 @@ export const Cell = memo((props) => {
|
|
|
89
105
|
// Alignment
|
|
90
106
|
const isRightAligned = horizontalAlignment === "right";
|
|
91
107
|
// Spacing
|
|
92
|
-
const indentSpacing = indent ? indent * INDENT_SPACING : 0;
|
|
108
|
+
const indentSpacing = indent ? indent * INDENT_SPACING * scale : 0;
|
|
93
109
|
const iconSpacing = DEFAULT_CELL_SPACING;
|
|
94
110
|
const showExpand = false;
|
|
95
111
|
const leftSpacing = getLeftSpacing(isCalculatedCell, expandable, isRightAligned ? showExpand : false, iconSpacing, scale);
|
|
96
|
-
const rightSpacing = getRightSpacing(showFilter, showArrow, isRightAligned ? false : showExpand, iconSpacing, scale);
|
|
112
|
+
const rightSpacing = getRightSpacing(showFilter, showArrow && !arrowOutside, isRightAligned ? false : showExpand, iconSpacing, scale);
|
|
97
113
|
// final cell width
|
|
98
114
|
const finalCellWidth = cellWidth ?? 0;
|
|
115
|
+
// Centred overflow: the spill area is `[x - overflowLeft, x - overflowLeft
|
|
116
|
+
// + cellWidth]`. Centred text stays centred on its own cell (a blocked
|
|
117
|
+
// side clips it); Center Across Selection centres on the whole span.
|
|
118
|
+
const centredSpill = enableOverflow && overflowLeft !== undefined
|
|
119
|
+
? (() => {
|
|
120
|
+
const spanLeft = x - overflowLeft;
|
|
121
|
+
if (centerOnSpan) {
|
|
122
|
+
return { spanLeft, boxLeft: spanLeft, boxWidth: finalCellWidth };
|
|
123
|
+
}
|
|
124
|
+
const reach = Math.max(overflowLeft, finalCellWidth - width - overflowLeft);
|
|
125
|
+
return { spanLeft, boxLeft: x - reach, boxWidth: width + 2 * reach };
|
|
126
|
+
})()
|
|
127
|
+
: undefined;
|
|
99
128
|
// text
|
|
100
129
|
const textWidth = _textWidth + indentSpacing;
|
|
101
130
|
// Reserve room for the CF icon on the left side of the cell. Only adds
|
|
@@ -107,7 +136,11 @@ export const Cell = memo((props) => {
|
|
|
107
136
|
// doesn't render past the cell's right edge when an iconSet is present.
|
|
108
137
|
const finalTextWidth = (overflowRight
|
|
109
138
|
? Math.max(textWidth, width)
|
|
110
|
-
: (
|
|
139
|
+
: (centredSpill
|
|
140
|
+
? centredSpill.boxWidth
|
|
141
|
+
: enableOverflow
|
|
142
|
+
? finalCellWidth
|
|
143
|
+
: width) -
|
|
111
144
|
rightSpacing -
|
|
112
145
|
leftSpacing -
|
|
113
146
|
indentSpacing) - cfIconReserve;
|
|
@@ -116,8 +149,10 @@ export const Cell = memo((props) => {
|
|
|
116
149
|
? textWidth > width
|
|
117
150
|
? x - (textWidth - width)
|
|
118
151
|
: x
|
|
119
|
-
:
|
|
120
|
-
|
|
152
|
+
: centredSpill
|
|
153
|
+
? centredSpill.boxLeft
|
|
154
|
+
: x +
|
|
155
|
+
(isRightAligned ? -indentSpacing - Math.floor((padding * scale) / 2) : 0);
|
|
121
156
|
// Text left padding
|
|
122
157
|
const textLeftPadding = textX +
|
|
123
158
|
leftSpacing +
|
|
@@ -131,7 +166,9 @@ export const Cell = memo((props) => {
|
|
|
131
166
|
? enableOverflow
|
|
132
167
|
? textX + (textWidth - finalCellWidth)
|
|
133
168
|
: textX
|
|
134
|
-
:
|
|
169
|
+
: centredSpill
|
|
170
|
+
? centredSpill.spanLeft
|
|
171
|
+
: textX;
|
|
135
172
|
const overflowWidth = finalCellWidth;
|
|
136
173
|
// Clip
|
|
137
174
|
const clipX = (enableOverflow ? overflowX : x) +
|
|
@@ -158,7 +195,9 @@ export const Cell = memo((props) => {
|
|
|
158
195
|
: fontSize;
|
|
159
196
|
// Wrap — disabled in stacked, shrink, or rotated modes. Justify /
|
|
160
197
|
// distributed alignment wraps implicitly, as in Excel.
|
|
161
|
-
const wrapRequested = wrapStrategy === "wrap" ||
|
|
198
|
+
const wrapRequested = wrapStrategy === "wrap" ||
|
|
199
|
+
alignmentImpliesWrap(horizontalAlignment) ||
|
|
200
|
+
isVerticalSpread;
|
|
162
201
|
const textWrapStrategy = isStackedVertical || shrinkEnabled || hasNumericRotation
|
|
163
202
|
? "none"
|
|
164
203
|
: wrapRequested
|
|
@@ -178,7 +217,7 @@ export const Cell = memo((props) => {
|
|
|
178
217
|
// the same numbers (#1141). Konva's `lineHeight` is a multiple of the
|
|
179
218
|
// font size.
|
|
180
219
|
const textLineMetrics = getLineMetrics({ fontFamily, fontSize: effectiveFontSize, bold, italic }, lineHeightMode, lineHeight, scale);
|
|
181
|
-
const
|
|
220
|
+
const baseTextLineHeight = effectiveFontSize > 0
|
|
182
221
|
? textLineMetrics.advance / effectiveFontSize
|
|
183
222
|
: lineHeight;
|
|
184
223
|
// The sizer fitted the row to Konva's text block exactly — one advance
|
|
@@ -186,6 +225,16 @@ export const Cell = memo((props) => {
|
|
|
186
225
|
// by the padding on both sides, its padding box is the cell, and every
|
|
187
226
|
// fitted line shows (#1182, #1199).
|
|
188
227
|
const textNode = getTextNodeBounds(y + cellSpacingY, height, padding);
|
|
228
|
+
// Vertical justify / distributed: spread the wrapped lines over the
|
|
229
|
+
// cell's inner height. The line count is estimated from the natural
|
|
230
|
+
// width against the wrap width (and any hard line breaks).
|
|
231
|
+
const wrapWidth = Math.max(1, finalTextWidth - 2 * padding);
|
|
232
|
+
const estimatedLineCount = shouldWrap
|
|
233
|
+
? Math.max((displayValue ?? "").split("\n").length, Math.ceil(_textWidth / wrapWidth))
|
|
234
|
+
: 1;
|
|
235
|
+
const textLineHeight = isVerticalSpread
|
|
236
|
+
? getVerticalSpreadLineHeight(estimatedLineCount, textNode.height - 2 * padding, effectiveFontSize, baseTextLineHeight)
|
|
237
|
+
: baseTextLineHeight;
|
|
189
238
|
// For rotated text, anchor the rotation pivot to an alignment-aware
|
|
190
239
|
// corner of the cell so the text stays inside instead of sliding off
|
|
191
240
|
// (Konva otherwise rotates around the Text's top-left at x/y).
|
|
@@ -208,16 +257,37 @@ export const Cell = memo((props) => {
|
|
|
208
257
|
})
|
|
209
258
|
: undefined;
|
|
210
259
|
const rotationDegrees = rotatedLayout?.rotation ?? getKonvaRotationDegrees(textRotation);
|
|
211
|
-
// CF data
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
260
|
+
// CF data bar — painted behind the text Group so the cell value stays
|
|
261
|
+
// readable on top of the bar (`paintDataBar` owns the geometry).
|
|
262
|
+
const dataBarSceneFunc = useCallback((context, shape) => {
|
|
263
|
+
if (!conditionalDataBar)
|
|
264
|
+
return;
|
|
265
|
+
const barStart = conditionalDataBar.barStart ??
|
|
266
|
+
(conditionalDataBar.direction === "rightToLeft"
|
|
267
|
+
? 1 - conditionalDataBar.fillFraction
|
|
268
|
+
: 0);
|
|
269
|
+
const barEnd = conditionalDataBar.barEnd ??
|
|
270
|
+
barStart + Math.max(0, Math.min(1, conditionalDataBar.fillFraction));
|
|
271
|
+
paintDataBar(context, { width: shape.width(), height: shape.height(), scale }, { ...conditionalDataBar, barStart, barEnd });
|
|
272
|
+
}, [conditionalDataBar, scale]);
|
|
273
|
+
// CF icon — Excel's vector icon at the cell's left edge, aligned with the
|
|
274
|
+
// cell's vertical alignment.
|
|
275
|
+
const cfIconShape = conditionalIcon
|
|
276
|
+
? getCfIcon(conditionalIcon.iconSet, conditionalIcon.iconId)
|
|
277
|
+
: undefined;
|
|
278
|
+
const cfIconSceneFunc = useCallback((context, shape) => {
|
|
279
|
+
if (!cfIconShape)
|
|
280
|
+
return;
|
|
281
|
+
const size = Math.min(CF_ICON_WIDTH * scale, shape.height() - 2 * scale);
|
|
282
|
+
if (size <= 0)
|
|
283
|
+
return;
|
|
284
|
+
const iconY = verticalAlignment === "top"
|
|
285
|
+
? scale
|
|
286
|
+
: verticalAlignment === "middle"
|
|
287
|
+
? (shape.height() - size) / 2
|
|
288
|
+
: shape.height() - size - 2 * scale;
|
|
289
|
+
paintCfIcon(context, cfIconShape, 2 * scale, iconY, size);
|
|
290
|
+
}, [cfIconShape, scale, verticalAlignment]);
|
|
221
291
|
// Use the segment-based renderer only when per-segment formatting
|
|
222
292
|
// exists AND none of the unsupported display modes are active.
|
|
223
293
|
// Rotation, stacked-vertical, shrink-to-fit, and chips stay on the
|
|
@@ -229,15 +299,249 @@ export const Cell = memo((props) => {
|
|
|
229
299
|
// own styling instead of being flattened; plain no-run cells with
|
|
230
300
|
// a cell-level baselineOffset keep the single-style approximation
|
|
231
301
|
// below (scaled font + top/bottom alignment).
|
|
302
|
+
// Shrink-to-fit and rotated cells keep their runs: the run painter takes
|
|
303
|
+
// the shrink factor and the rotated layout (stacked text stays plain).
|
|
232
304
|
const useRichTextRunPath = !showChip &&
|
|
305
|
+
!isStackedVertical &&
|
|
306
|
+
hasRenderableRuns(textFormatRuns ?? undefined);
|
|
307
|
+
const shrinkFactor = fontSize > 0 ? effectiveFontSize / fontSize : 1;
|
|
308
|
+
const scriptOffsetY = getScriptTextOffsetY({
|
|
309
|
+
baselineOffset,
|
|
310
|
+
verticalAlign: effectiveVerticalAlignment,
|
|
311
|
+
baseFontSize: baseFontSize * shrinkFactor,
|
|
312
|
+
scriptFontSize: effectiveFontSize,
|
|
313
|
+
lineHeight: textLineHeight,
|
|
314
|
+
});
|
|
315
|
+
// Double / accounting underlines on a single unrotated line (the default
|
|
316
|
+
// Text path); wrapped, rotated or rich-text cells keep Konva's single line.
|
|
317
|
+
const paintStyledUnderline = hasStyledUnderline &&
|
|
318
|
+
!useRichTextRunPath &&
|
|
319
|
+
!showChip &&
|
|
320
|
+
!shouldWrap &&
|
|
321
|
+
!isStackedVertical &&
|
|
322
|
+
!hasNumericRotation;
|
|
323
|
+
const textDirection = readingOrder === "rtl" ? "rtl" : readingOrder === "ltr" ? "ltr" : "inherit";
|
|
324
|
+
const underlineSceneFunc = useCallback((context) => {
|
|
325
|
+
if (!paintStyledUnderline || !underlineStyle)
|
|
326
|
+
return;
|
|
327
|
+
const advance = textLineMetrics.advance;
|
|
328
|
+
const innerLeft = textLeftPadding + padding;
|
|
329
|
+
const innerWidth = Math.max(0, finalTextWidth - 2 * padding);
|
|
330
|
+
const glyphWidth = Math.min(_textWidth, innerWidth);
|
|
331
|
+
const textStart = canvasTextAlign === "right"
|
|
332
|
+
? innerLeft + innerWidth - glyphWidth
|
|
333
|
+
: canvasTextAlign === "center"
|
|
334
|
+
? innerLeft + (innerWidth - glyphWidth) / 2
|
|
335
|
+
: innerLeft;
|
|
336
|
+
const innerTop = textNode.y + padding;
|
|
337
|
+
const innerHeight = textNode.height - 2 * padding;
|
|
338
|
+
const lineTop = effectiveVerticalAlignment === "top"
|
|
339
|
+
? innerTop
|
|
340
|
+
: effectiveVerticalAlignment === "middle"
|
|
341
|
+
? innerTop + (innerHeight - advance) / 2
|
|
342
|
+
: innerTop + innerHeight - advance;
|
|
343
|
+
const strokes = getUnderlineStrokes({
|
|
344
|
+
style: underlineStyle,
|
|
345
|
+
textX: textStart,
|
|
346
|
+
textWidth: glyphWidth,
|
|
347
|
+
cellX: x,
|
|
348
|
+
cellWidth: width,
|
|
349
|
+
baselineY: lineTop + advance / 2 + effectiveFontSize * 0.35,
|
|
350
|
+
fontSize: effectiveFontSize,
|
|
351
|
+
scale,
|
|
352
|
+
});
|
|
353
|
+
context.beginPath();
|
|
354
|
+
for (const { x1, x2, y: strokeY } of strokes) {
|
|
355
|
+
const yy = Math.round(strokeY) + 0.5;
|
|
356
|
+
context.moveTo(x1, yy);
|
|
357
|
+
context.lineTo(x2, yy);
|
|
358
|
+
}
|
|
359
|
+
context.setAttr("strokeStyle", color ?? defaultColor);
|
|
360
|
+
context.setAttr("lineWidth", Math.max(1, scale));
|
|
361
|
+
context.stroke();
|
|
362
|
+
}, [
|
|
363
|
+
paintStyledUnderline,
|
|
364
|
+
underlineStyle,
|
|
365
|
+
textLineMetrics.advance,
|
|
366
|
+
textLeftPadding,
|
|
367
|
+
padding,
|
|
368
|
+
finalTextWidth,
|
|
369
|
+
_textWidth,
|
|
370
|
+
canvasTextAlign,
|
|
371
|
+
textNode.y,
|
|
372
|
+
textNode.height,
|
|
373
|
+
effectiveVerticalAlignment,
|
|
374
|
+
x,
|
|
375
|
+
width,
|
|
376
|
+
effectiveFontSize,
|
|
377
|
+
scale,
|
|
378
|
+
color,
|
|
379
|
+
defaultColor,
|
|
380
|
+
]);
|
|
381
|
+
// Excel's `*x` fill and `_x` / `?` reserves (accounting `$` left, number
|
|
382
|
+
// right): one unrotated, unwrapped line whose parts are placed with
|
|
383
|
+
// measured glyph widths. Every other display mode keeps the plain string.
|
|
384
|
+
const paintNumberFormatLayout = !!numberFormatLayout &&
|
|
385
|
+
!useRichTextRunPath &&
|
|
386
|
+
!showChip &&
|
|
387
|
+
!shouldWrap &&
|
|
388
|
+
!isStackedVertical &&
|
|
389
|
+
!hasNumericRotation &&
|
|
233
390
|
!shrinkEnabled &&
|
|
391
|
+
!isFillAlignment &&
|
|
392
|
+
!enableOverflow &&
|
|
393
|
+
textDirection !== "rtl";
|
|
394
|
+
const numberFormatSceneFunc = useCallback((context, shape) => {
|
|
395
|
+
if (!numberFormatLayout)
|
|
396
|
+
return;
|
|
397
|
+
const ctx = context;
|
|
398
|
+
ctx.setAttr("font", `${textStyle} normal ${effectiveFontSize}px ${fontFamily ?? "sans-serif"}`);
|
|
399
|
+
ctx.setAttr("textBaseline", "alphabetic");
|
|
400
|
+
ctx.setAttr("textAlign", "left");
|
|
401
|
+
ctx.setAttr("fillStyle", color ?? defaultColor);
|
|
402
|
+
const innerWidth = Math.max(0, shape.width() - 2 * padding);
|
|
403
|
+
const measure = (text) => ctx.measureText(text).width;
|
|
404
|
+
const layout = layoutNumberFormatParts({
|
|
405
|
+
parts: numberFormatLayout,
|
|
406
|
+
measure,
|
|
407
|
+
availableWidth: innerWidth,
|
|
408
|
+
align: canvasTextAlign,
|
|
409
|
+
}) ?? {
|
|
410
|
+
// The fixed parts alone overflow the cell: paint the plain string
|
|
411
|
+
// aligned the way the text node would (the group clips it).
|
|
412
|
+
runs: [
|
|
413
|
+
{
|
|
414
|
+
text: value ?? "",
|
|
415
|
+
x: canvasTextAlign === "right"
|
|
416
|
+
? innerWidth - measure(value ?? "")
|
|
417
|
+
: canvasTextAlign === "center"
|
|
418
|
+
? (innerWidth - measure(value ?? "")) / 2
|
|
419
|
+
: 0,
|
|
420
|
+
width: measure(value ?? ""),
|
|
421
|
+
},
|
|
422
|
+
],
|
|
423
|
+
start: 0,
|
|
424
|
+
end: innerWidth,
|
|
425
|
+
};
|
|
426
|
+
// Konva's text baseline: the line box is `lineHeight × fontSize` tall,
|
|
427
|
+
// aligned in the node, glyphs centred on it by the font's M box.
|
|
428
|
+
const lineHeightPx = textLineHeight * effectiveFontSize;
|
|
429
|
+
const metrics = ctx.measureText("M");
|
|
430
|
+
const ascent = metrics.fontBoundingBoxAscent ?? metrics.actualBoundingBoxAscent ?? 0;
|
|
431
|
+
const descent = metrics.fontBoundingBoxDescent ??
|
|
432
|
+
metrics.actualBoundingBoxDescent ??
|
|
433
|
+
0;
|
|
434
|
+
const alignY = effectiveVerticalAlignment === "top"
|
|
435
|
+
? 0
|
|
436
|
+
: effectiveVerticalAlignment === "middle"
|
|
437
|
+
? (shape.height() - lineHeightPx - 2 * padding) / 2
|
|
438
|
+
: shape.height() - lineHeightPx - 2 * padding;
|
|
439
|
+
const baselineY = padding + alignY + (ascent - descent) / 2 + lineHeightPx / 2;
|
|
440
|
+
for (const run of layout.runs) {
|
|
441
|
+
ctx.fillText(run.text, padding + run.x, baselineY);
|
|
442
|
+
}
|
|
443
|
+
if (!underline && !strikethrough)
|
|
444
|
+
return;
|
|
445
|
+
context.beginPath();
|
|
446
|
+
const textStart = padding + layout.start;
|
|
447
|
+
const textWidthPx = layout.end - layout.start;
|
|
448
|
+
if (underline) {
|
|
449
|
+
for (const { x1, x2, y: strokeY } of getUnderlineStrokes({
|
|
450
|
+
style: underlineStyle ?? "single",
|
|
451
|
+
textX: textStart,
|
|
452
|
+
textWidth: textWidthPx,
|
|
453
|
+
cellX: x - shape.x(),
|
|
454
|
+
cellWidth: width,
|
|
455
|
+
baselineY,
|
|
456
|
+
fontSize: effectiveFontSize,
|
|
457
|
+
scale,
|
|
458
|
+
})) {
|
|
459
|
+
const yy = Math.round(strokeY) + 0.5;
|
|
460
|
+
context.moveTo(x1, yy);
|
|
461
|
+
context.lineTo(x2, yy);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (strikethrough) {
|
|
465
|
+
const yy = Math.round(baselineY - effectiveFontSize * 0.3) + 0.5;
|
|
466
|
+
context.moveTo(textStart, yy);
|
|
467
|
+
context.lineTo(textStart + textWidthPx, yy);
|
|
468
|
+
}
|
|
469
|
+
ctx.setAttr("strokeStyle", color ?? defaultColor);
|
|
470
|
+
ctx.setAttr("lineWidth", Math.max(1, scale));
|
|
471
|
+
context.stroke();
|
|
472
|
+
}, [
|
|
473
|
+
numberFormatLayout,
|
|
474
|
+
textStyle,
|
|
475
|
+
effectiveFontSize,
|
|
476
|
+
fontFamily,
|
|
477
|
+
color,
|
|
478
|
+
defaultColor,
|
|
479
|
+
padding,
|
|
480
|
+
canvasTextAlign,
|
|
481
|
+
value,
|
|
482
|
+
textLineHeight,
|
|
483
|
+
effectiveVerticalAlignment,
|
|
484
|
+
underline,
|
|
485
|
+
strikethrough,
|
|
486
|
+
underlineStyle,
|
|
487
|
+
x,
|
|
488
|
+
width,
|
|
489
|
+
scale,
|
|
490
|
+
]);
|
|
491
|
+
// Excel's horizontal `distributed`: wrapped lines spread edge to edge
|
|
492
|
+
// (the last one too), a lone word centred, the indent kept on both sides.
|
|
493
|
+
const paintDistributed = horizontalAlignment === "distributed" &&
|
|
494
|
+
!useRichTextRunPath &&
|
|
495
|
+
!showChip &&
|
|
234
496
|
!isStackedVertical &&
|
|
235
497
|
!hasNumericRotation &&
|
|
236
|
-
|
|
498
|
+
!shrinkEnabled;
|
|
499
|
+
const distributedSceneFunc = useCallback((context, shape) => {
|
|
500
|
+
const ctx = context;
|
|
501
|
+
ctx.setAttr("font", `${textStyle} normal ${effectiveFontSize}px ${fontFamily ?? "sans-serif"}`);
|
|
502
|
+
ctx.setAttr("textBaseline", "alphabetic");
|
|
503
|
+
ctx.setAttr("textAlign", "left");
|
|
504
|
+
ctx.setAttr("fillStyle", color ?? defaultColor);
|
|
505
|
+
const innerWidth = Math.max(0, shape.width() - 2 * padding - indentSpacing);
|
|
506
|
+
const lines = layoutDistributedLines(displayValue ?? "", innerWidth, (text) => ctx.measureText(text).width);
|
|
507
|
+
const lineHeightPx = textLineHeight * effectiveFontSize;
|
|
508
|
+
const metrics = ctx.measureText("M");
|
|
509
|
+
const ascent = metrics.fontBoundingBoxAscent ?? metrics.actualBoundingBoxAscent ?? 0;
|
|
510
|
+
const descent = metrics.fontBoundingBoxDescent ??
|
|
511
|
+
metrics.actualBoundingBoxDescent ??
|
|
512
|
+
0;
|
|
513
|
+
const blockHeight = lines.length * lineHeightPx;
|
|
514
|
+
const alignY = effectiveVerticalAlignment === "top"
|
|
515
|
+
? 0
|
|
516
|
+
: effectiveVerticalAlignment === "middle"
|
|
517
|
+
? (shape.height() - blockHeight - 2 * padding) / 2
|
|
518
|
+
: shape.height() - blockHeight - 2 * padding;
|
|
519
|
+
lines.forEach((words, lineIndex) => {
|
|
520
|
+
const baselineY = padding +
|
|
521
|
+
alignY +
|
|
522
|
+
lineIndex * lineHeightPx +
|
|
523
|
+
(ascent - descent) / 2 +
|
|
524
|
+
lineHeightPx / 2;
|
|
525
|
+
for (const word of words) {
|
|
526
|
+
ctx.fillText(word.text, padding + word.x, baselineY);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
}, [
|
|
530
|
+
textStyle,
|
|
531
|
+
effectiveFontSize,
|
|
532
|
+
fontFamily,
|
|
533
|
+
color,
|
|
534
|
+
defaultColor,
|
|
535
|
+
padding,
|
|
536
|
+
indentSpacing,
|
|
537
|
+
displayValue,
|
|
538
|
+
textLineHeight,
|
|
539
|
+
effectiveVerticalAlignment,
|
|
540
|
+
]);
|
|
237
541
|
return (React.createElement(React.Fragment, null,
|
|
238
542
|
enableOverflow && showGridLines ? (React.createElement(Shape, { x: x, y: y, width: width, height: height, sceneFunc: overflowFillFunc, listening: false })) : null,
|
|
239
|
-
conditionalDataBar
|
|
240
|
-
conditionalIcon ? (React.createElement(
|
|
543
|
+
conditionalDataBar ? (React.createElement(Shape, { x: x, y: y, width: width, height: height, sceneFunc: dataBarSceneFunc, listening: false, perfectDrawEnabled: false })) : null,
|
|
544
|
+
conditionalIcon && cfIconShape ? (React.createElement(Shape, { x: x, y: y, width: width, height: height, sceneFunc: cfIconSceneFunc, listening: false, perfectDrawEnabled: false })) : null,
|
|
241
545
|
showCheckbox ? (React.createElement(CellCheckbox, { x: x, y: y, scale: scale, width: width, height: height, checked: isChecked, onChange: onCheckboxClick, rowIndex: props.rowIndex, columnIndex: props.columnIndex, verticalAlignment: verticalAlignment, horizontalAlignment: horizontalAlignment })) : showText ? (React.createElement(Group, { clipY: y + borderWidth, clipHeight: height, clipX: clipX, clipWidth: clipWidth }, showChip ? (React.createElement(Label, { width: width - CHIP_SPACING * scale, height: CHIP_HEIGHT * scale, x: textLeftPadding, y: getChipVerticalPosition(y, height, scale, verticalAlignment), offsetX: -CHIP_SPACING * scale, offsetY: -CHIP_SPACING * scale },
|
|
242
546
|
React.createElement(Tag, { height: CHIP_HEIGHT * scale, fill: chipBackgroundColor, cornerRadius: CHIP_RADIUS * scale, perfectDrawEnabled: false, hitStrokeWidth: 0 }),
|
|
243
547
|
React.createElement(Text, { text: displayValue, fill: color ?? defaultColor, verticalAlign: effectiveVerticalAlignment, align: canvasTextAlign, fontFamily: fontFamily, fontStyle: textStyle, fontSize: fontSize, textDecoration: textDecoration, padding: padding, lineHeight: 1, offsetY: -1, perfectDrawEnabled: false, wrap: "none" }))) : useRichTextRunPath ? (
|
|
@@ -251,14 +555,16 @@ export const Cell = memo((props) => {
|
|
|
251
555
|
// lines, and `textFormatRuns` indices reference the raw
|
|
252
556
|
// string. `normalizeDisplayText` is bypassed because the
|
|
253
557
|
// renderer handles multi-line layout itself.
|
|
254
|
-
React.createElement(CellTextRuns, { x: textLeftPadding, y: textNode.y, width: finalTextWidth, height: textNode.height, padding: padding, value: displayValue ?? "", runs: textFormatRuns ?? [], baseColor: color ?? defaultColor, baseFontFamily: fontFamily,
|
|
558
|
+
React.createElement(CellTextRuns, { x: rotatedLayout?.x ?? textLeftPadding, y: rotatedLayout?.y ?? textNode.y, width: rotatedLayout?.width ?? finalTextWidth, height: rotatedLayout ? approxTextHeight : textNode.height, rotation: rotatedLayout ? rotationDegrees : undefined, fontScale: scale * shrinkFactor, padding: padding, value: displayValue ?? "", runs: textFormatRuns ?? [], baseColor: color ?? defaultColor, baseFontFamily: fontFamily,
|
|
255
559
|
// UN-scaled size on purpose: when the cell carries a
|
|
256
560
|
// baselineOffset the segment resolver applies the
|
|
257
561
|
// sup/sub font scale itself (exactly once). Shrink-to-
|
|
258
562
|
// fit never reaches this path, so `baseFontSize` equals
|
|
259
563
|
// `effectiveFontSize` for every other cell.
|
|
260
|
-
baseFontSize: baseFontSize, baseBold: !!bold, baseItalic: !!italic, baseUnderline: !!underline, baseStrikethrough: !!strikethrough, baseBaselineOffset: baselineOffset ?? undefined, horizontalAlign: canvasTextAlign, verticalAlign: effectiveVerticalAlignment, lineHeight: lineHeight, lineHeightMode: lineHeightMode, scale: scale, wrap: shouldWrap, theme: theme })) : (React.createElement(
|
|
261
|
-
|
|
564
|
+
baseFontSize: baseFontSize * shrinkFactor, baseBold: !!bold, baseItalic: !!italic, baseUnderline: !!underline, baseStrikethrough: !!strikethrough, baseBaselineOffset: baselineOffset ?? undefined, horizontalAlign: rotatedLayout ? "left" : canvasTextAlign, verticalAlign: rotatedLayout ? "top" : effectiveVerticalAlignment, lineHeight: lineHeight, lineHeightMode: lineHeightMode, scale: scale, wrap: shouldWrap && !rotatedLayout, theme: theme })) : paintDistributed ? (React.createElement(Shape, { x: textLeftPadding, y: textNode.y, width: finalTextWidth, height: textNode.height, sceneFunc: distributedSceneFunc, listening: false, perfectDrawEnabled: false })) : paintNumberFormatLayout ? (React.createElement(Shape, { x: textLeftPadding, y: textNode.y, width: finalTextWidth, height: textNode.height, sceneFunc: numberFormatSceneFunc, listening: false, perfectDrawEnabled: false })) : (React.createElement(React.Fragment, null,
|
|
565
|
+
React.createElement(Text, { x: rotatedLayout?.x ?? textLeftPadding, y: rotatedLayout?.y ?? textNode.y, offsetX: rotatedLayout?.offsetX, offsetY: (rotatedLayout?.offsetY ?? 0) + scriptOffsetY, height: rotatedLayout ? approxTextHeight : textNode.height, width: rotatedLayout?.width ?? finalTextWidth, text: displayValue, fill: color ?? defaultColor, verticalAlign: rotatedLayout ? "top" : effectiveVerticalAlignment, align: rotatedLayout ? "left" : canvasTextAlign, fontFamily: fontFamily, fontStyle: textStyle, fontSize: effectiveFontSize, textDecoration: paintStyledUnderline ? textDecorationFor(false) : textDecoration, padding: padding, wrap: textWrapStrategy, lineHeight: textLineHeight, rotation: rotationDegrees, direction: textDirection, hitStrokeWidth: 0, perfectDrawEnabled: false }),
|
|
566
|
+
paintStyledUnderline ? (React.createElement(Shape, { sceneFunc: underlineSceneFunc, listening: false, perfectDrawEnabled: false })) : null)))) : imageUrl ? (React.createElement(Image, { x: x, y: y, spacing: 1, width: width, height: height, url: imageUrl, perfectDrawEnabled: false, horizontalAlignment: canvasTextAlign === "justify" ? "left" : canvasTextAlign, verticalAlignment: verticalAlignment })) : null,
|
|
567
|
+
showFilter ? (React.createElement(CellFilter, { scale: scale, isActive: isFilterActive, onClick: readonly ? undefined : onFilterClick, rowIndex: props.rowIndex, columnIndex: props.columnIndex, x: x, y: y, height: height, width: width, filterIconColor: filterIconColor, verticalAlignment: verticalAlignment, backgroundColor: defaultBackgroundColor, sortOrder: sortOrder })) : showArrow && !readonly ? (React.createElement(CellArrow, { scale: scale, onClick: onArrowClick ?? onRequestEdit, rowIndex: rowIndex, columnIndex: columnIndex, x: arrowOutside ? x + (ARROW_DOWN_WIDTH + 4) * scale : x, y: y, height: height, width: width, verticalAlignment: verticalAlignment })) : null,
|
|
262
568
|
expandable ? (React.createElement(CellGroup, { scale: scale, onClick: (cell) => {
|
|
263
569
|
onExpandCollapse?.(cell, !expanded);
|
|
264
570
|
}, rowIndex: rowIndex, columnIndex: columnIndex, x: x + indentSpacing, y: y, height: height, width: width, verticalAlignment: verticalAlignment, expanded: expanded, theme: theme })) : null,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type NumberFormatLayoutPart } from "@rowsncolumns/utils";
|
|
2
|
+
export type NumberFormatRun = {
|
|
3
|
+
text: string;
|
|
4
|
+
x: number;
|
|
5
|
+
width: number;
|
|
6
|
+
};
|
|
7
|
+
export type NumberFormatRunLayout = {
|
|
8
|
+
runs: NumberFormatRun[];
|
|
9
|
+
/** Left edge of the first painted glyph (or reserve). */
|
|
10
|
+
start: number;
|
|
11
|
+
/** Right edge of the last one. */
|
|
12
|
+
end: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Position the parts inside `availableWidth`. Parts before the (first) fill
|
|
16
|
+
* start at the left edge and parts after it end at the right edge, with the
|
|
17
|
+
* fill character repeated in between as many whole times as it fits —
|
|
18
|
+
* Excel's accounting format (`$` left, number right). Without a fill the
|
|
19
|
+
* block is aligned by `align`. Returns `undefined` when the fixed parts
|
|
20
|
+
* alone do not fit, so the caller keeps its plain-text paint.
|
|
21
|
+
*/
|
|
22
|
+
export declare const layoutNumberFormatParts: ({ parts, measure, availableWidth, align, }: {
|
|
23
|
+
parts: readonly NumberFormatLayoutPart[];
|
|
24
|
+
measure: (text: string) => number;
|
|
25
|
+
availableWidth: number;
|
|
26
|
+
align: "left" | "center" | "right" | "justify";
|
|
27
|
+
}) => NumberFormatRunLayout | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* The layout parts for a cell, or `undefined` when the pattern has no
|
|
30
|
+
* width-aware token or when the parts would paint a different string than
|
|
31
|
+
* the cell's formatted value (another engine, locale or conditional number
|
|
32
|
+
* format produced it) — the plain string then stays authoritative.
|
|
33
|
+
*/
|
|
34
|
+
export declare const getCellNumberFormatLayout: (pattern: string | null | undefined, value: number | string | null | undefined, formattedValue: string | null | undefined, locale?: string) => NumberFormatLayoutPart[] | undefined;
|
|
35
|
+
//# sourceMappingURL=number-format-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-format-layout.d.ts","sourceRoot":"","sources":["../../../../components/cell/number-format-layout.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,KAAK,sBAAsB,EAG5B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GAAI,4CAKrC;IACD,KAAK,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACzC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;CAChD,KAAG,qBAAqB,GAAG,SA+D3B,CAAC;AAKF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GACpC,SAAS,MAAM,GAAG,IAAI,GAAG,SAAS,EAClC,OAAO,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,gBAAgB,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,SAAS,MAAM,KACd,sBAAsB,EAAE,GAAG,SAa7B,CAAC"}
|