@ricsam/react-spreadsheets 0.0.1 → 0.0.3
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/README.md +31 -6
- package/dist/cjs/lib.cjs +127 -20
- package/dist/cjs/lib.cjs.map +6 -5
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/lib.mjs +127 -20
- package/dist/mjs/lib.mjs.map +6 -5
- package/dist/mjs/package.json +1 -1
- package/dist/styles.css +231 -73
- package/dist/types/lib.d.ts +1 -0
- package/dist/types/utils/contrast-text.d.ts +36 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -88,11 +88,11 @@ top of `FormulaSheet`.
|
|
|
88
88
|
|
|
89
89
|
## Theming
|
|
90
90
|
|
|
91
|
-
All colors, fonts and sizes are CSS custom properties declared on
|
|
92
|
-
|
|
91
|
+
All colors, fonts and sizes are CSS custom properties declared on `:root`, so
|
|
92
|
+
you can override them from **any** ancestor of the grid:
|
|
93
93
|
|
|
94
94
|
```css
|
|
95
|
-
.
|
|
95
|
+
.my-panel {
|
|
96
96
|
--rsp-accent: #7c3aed;
|
|
97
97
|
--rsp-bg: #ffffff;
|
|
98
98
|
--rsp-header-bg: #f4f4f5;
|
|
@@ -100,9 +100,34 @@ Override them anywhere in your cascade:
|
|
|
100
100
|
}
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
Light and dark
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
### Light and dark
|
|
104
|
+
|
|
105
|
+
Colors resolve through `light-dark()`, which follows the inherited
|
|
106
|
+
`color-scheme`. By default the grid follows the user's OS preference.
|
|
107
|
+
|
|
108
|
+
The grid deliberately does **not** declare `color-scheme` on `.rsp-root`, so a
|
|
109
|
+
pin you set on an ancestor is respected. Pin a subtree with plain CSS:
|
|
110
|
+
|
|
111
|
+
```css
|
|
112
|
+
.workbook-stage { color-scheme: light; } /* always light */
|
|
113
|
+
.workbook-stage { color-scheme: dark; } /* always dark */
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
or with the bundled helper classes:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
<FormulaWorkbook className="rsp-theme-dark" ... />
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Canvas gridlines and every DOM surface follow the same resolved scheme.
|
|
123
|
+
|
|
124
|
+
### Cell fills stay readable
|
|
125
|
+
|
|
126
|
+
A `backgroundColor` coming from your document model is usually a single literal
|
|
127
|
+
color with no dark-mode variant. To stop light fills from pairing with the dark
|
|
128
|
+
theme's near-white text, the grid derives a readable ink for any cell that sets
|
|
129
|
+
a background but no explicit `color`. Set `color` yourself to opt out, and use
|
|
130
|
+
`getContrastingTextColor` if you want the same behaviour elsewhere.
|
|
106
131
|
|
|
107
132
|
Per-cell styling is done in JS:
|
|
108
133
|
|
package/dist/cjs/lib.cjs
CHANGED
|
@@ -92,6 +92,7 @@ __export(exports_lib, {
|
|
|
92
92
|
getCellReference: () => getCellReference,
|
|
93
93
|
getCellSnapAnchorFromRect: () => getCellSnapAnchorFromRect,
|
|
94
94
|
getColumnEdgePosition: () => getColumnEdgePosition,
|
|
95
|
+
getContrastingTextColor: () => getContrastingTextColor,
|
|
95
96
|
getNearestColumnEdgeIndex: () => getNearestColumnEdgeIndex,
|
|
96
97
|
getNearestRowEdgeIndex: () => getNearestRowEdgeIndex,
|
|
97
98
|
getRectFromCellSnapAnchor: () => getRectFromCellSnapAnchor,
|
|
@@ -105,7 +106,9 @@ __export(exports_lib, {
|
|
|
105
106
|
normalizeCellSnapAnchor: () => normalizeCellSnapAnchor,
|
|
106
107
|
onCellEditError: () => onCellEditError,
|
|
107
108
|
parseCellReference: () => parseCellReference,
|
|
109
|
+
parseCssColor: () => parseCssColor,
|
|
108
110
|
queueCellEditError: () => queueCellEditError,
|
|
111
|
+
relativeLuminance: () => relativeLuminance,
|
|
109
112
|
resizeCellSnapAnchorToNearestEdges: () => resizeCellSnapAnchorToNearestEdges,
|
|
110
113
|
rowToLetter: () => rowToLetter,
|
|
111
114
|
useClickAway: () => useClickAway,
|
|
@@ -174,6 +177,85 @@ function useColorScheme(element) {
|
|
|
174
177
|
return scheme;
|
|
175
178
|
}
|
|
176
179
|
|
|
180
|
+
// src/utils/contrast-text.ts
|
|
181
|
+
var DARK_INK = "#101828";
|
|
182
|
+
var LIGHT_INK = "#ffffff";
|
|
183
|
+
var NAMED_COLORS = {
|
|
184
|
+
white: [255, 255, 255],
|
|
185
|
+
black: [0, 0, 0],
|
|
186
|
+
red: [255, 0, 0],
|
|
187
|
+
green: [0, 128, 0],
|
|
188
|
+
blue: [0, 0, 255],
|
|
189
|
+
yellow: [255, 255, 0],
|
|
190
|
+
orange: [255, 165, 0],
|
|
191
|
+
purple: [128, 0, 128],
|
|
192
|
+
gray: [128, 128, 128],
|
|
193
|
+
grey: [128, 128, 128],
|
|
194
|
+
silver: [192, 192, 192]
|
|
195
|
+
};
|
|
196
|
+
var clamp255 = (value) => Math.min(255, Math.max(0, value));
|
|
197
|
+
function parseCssColor(input) {
|
|
198
|
+
const value = input.trim().toLowerCase();
|
|
199
|
+
if (!value || value === "transparent" || value === "currentcolor" || value === "inherit") {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (value.includes("var(") || value.includes("color-mix(") || value.includes("gradient")) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const named = NAMED_COLORS[value];
|
|
206
|
+
if (named) {
|
|
207
|
+
return { r: named[0], g: named[1], b: named[2], a: 1 };
|
|
208
|
+
}
|
|
209
|
+
if (value.startsWith("#")) {
|
|
210
|
+
const hex = value.slice(1);
|
|
211
|
+
const expand = (part) => parseInt(part + part, 16);
|
|
212
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
213
|
+
const r = expand(hex[0]);
|
|
214
|
+
const g = expand(hex[1]);
|
|
215
|
+
const b = expand(hex[2]);
|
|
216
|
+
const a = hex.length === 4 ? expand(hex[3]) / 255 : 1;
|
|
217
|
+
return Number.isNaN(r + g + b) ? undefined : { r, g, b, a };
|
|
218
|
+
}
|
|
219
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
220
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
221
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
222
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
223
|
+
const a = hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1;
|
|
224
|
+
return Number.isNaN(r + g + b) ? undefined : { r, g, b, a };
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const rgbMatch = value.match(/^rgba?\(([^)]+)\)$/);
|
|
229
|
+
if (rgbMatch) {
|
|
230
|
+
const parts = rgbMatch[1].split(/[\s,/]+/).filter(Boolean);
|
|
231
|
+
if (parts.length < 3)
|
|
232
|
+
return;
|
|
233
|
+
const toChannel = (part) => part.endsWith("%") ? parseFloat(part) / 100 * 255 : parseFloat(part);
|
|
234
|
+
const r = toChannel(parts[0]);
|
|
235
|
+
const g = toChannel(parts[1]);
|
|
236
|
+
const b = toChannel(parts[2]);
|
|
237
|
+
const alphaPart = parts[3];
|
|
238
|
+
const a = alphaPart === undefined ? 1 : alphaPart.endsWith("%") ? parseFloat(alphaPart) / 100 : parseFloat(alphaPart);
|
|
239
|
+
if ([r, g, b, a].some((channel) => Number.isNaN(channel)))
|
|
240
|
+
return;
|
|
241
|
+
return { r: clamp255(r), g: clamp255(g), b: clamp255(b), a };
|
|
242
|
+
}
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
function relativeLuminance({ r, g, b }) {
|
|
246
|
+
const channel = (value) => {
|
|
247
|
+
const srgb = value / 255;
|
|
248
|
+
return srgb <= 0.03928 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);
|
|
249
|
+
};
|
|
250
|
+
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
|
|
251
|
+
}
|
|
252
|
+
function getContrastingTextColor(background) {
|
|
253
|
+
const color = parseCssColor(background);
|
|
254
|
+
if (!color || color.a < 0.5)
|
|
255
|
+
return;
|
|
256
|
+
return relativeLuminance(color) > 0.45 ? DARK_INK : LIGHT_INK;
|
|
257
|
+
}
|
|
258
|
+
|
|
177
259
|
// src/utils/cn.ts
|
|
178
260
|
function cn(...values) {
|
|
179
261
|
const out = [];
|
|
@@ -1352,16 +1434,27 @@ var resizeCellSnapAnchorToNearestEdges = (anchor, rect, handle, columnWidths, ro
|
|
|
1352
1434
|
|
|
1353
1435
|
// src/spreadsheet/spreadsheet.tsx
|
|
1354
1436
|
var jsx_runtime3 = require("react/jsx-runtime");
|
|
1355
|
-
var resolveGridLineColor = (
|
|
1356
|
-
const fallback = colorScheme === "dark" ? "#
|
|
1357
|
-
if (!
|
|
1437
|
+
var resolveGridLineColor = (probe, colorScheme) => {
|
|
1438
|
+
const fallback = colorScheme === "dark" ? "#232c3d" : "#e8ebf1";
|
|
1439
|
+
if (!probe || typeof getComputedStyle !== "function") {
|
|
1358
1440
|
return fallback;
|
|
1359
1441
|
}
|
|
1360
|
-
const
|
|
1361
|
-
if (!
|
|
1442
|
+
const resolved = getComputedStyle(probe).color?.trim();
|
|
1443
|
+
if (!resolved || resolved.includes("light-dark(") || resolved === "rgba(0, 0, 0, 0)") {
|
|
1362
1444
|
return fallback;
|
|
1363
1445
|
}
|
|
1364
|
-
return
|
|
1446
|
+
return resolved;
|
|
1447
|
+
};
|
|
1448
|
+
var withReadableTextColor = (style) => {
|
|
1449
|
+
if (!style?.backgroundColor || style.color)
|
|
1450
|
+
return style;
|
|
1451
|
+
const background = style.backgroundColor;
|
|
1452
|
+
if (typeof background !== "string")
|
|
1453
|
+
return style;
|
|
1454
|
+
const color = getContrastingTextColor(background);
|
|
1455
|
+
if (!color)
|
|
1456
|
+
return style;
|
|
1457
|
+
return { ...style, color };
|
|
1365
1458
|
};
|
|
1366
1459
|
var hasBorderStyle = (style) => Boolean(style?.borderColor || style?.borderTopColor || style?.borderRightColor || style?.borderBottomColor || style?.borderLeftColor);
|
|
1367
1460
|
var CellComponent = import_react4.default.memo(({
|
|
@@ -1404,7 +1497,7 @@ var CellComponent = import_react4.default.memo(({
|
|
|
1404
1497
|
isSelected,
|
|
1405
1498
|
isBeingEdited: isBeingEdited || false
|
|
1406
1499
|
};
|
|
1407
|
-
const customStyle = customCellStyle?.(cellStyleCallbackData);
|
|
1500
|
+
const customStyle = withReadableTextColor(customCellStyle?.(cellStyleCallbackData));
|
|
1408
1501
|
const canHaveFillHandle = import_selection_manager.useSelectionManager(selectionManager, () => {
|
|
1409
1502
|
return selectionManager.canCellHaveFillHandle({
|
|
1410
1503
|
row: rowIndex,
|
|
@@ -1436,7 +1529,8 @@ var CellComponent = import_react4.default.memo(({
|
|
|
1436
1529
|
top: y,
|
|
1437
1530
|
width: getColumnWidth(col) - 1,
|
|
1438
1531
|
height: getRowHeight(row) - 1,
|
|
1439
|
-
...customStyle
|
|
1532
|
+
...customStyle,
|
|
1533
|
+
...isBeingEdited ? { backgroundColor: undefined, color: undefined } : null
|
|
1440
1534
|
},
|
|
1441
1535
|
children: [
|
|
1442
1536
|
isBeingEdited ? /* @__PURE__ */ jsx_runtime3.jsx("textarea", {
|
|
@@ -1686,6 +1780,7 @@ var Spreadsheet = import_react4.forwardRef(({
|
|
|
1686
1780
|
const [{ width, height }, containerRef] = useResizeObserver();
|
|
1687
1781
|
const [containerDivRef, setContainerDivRef] = import_react4.useState(null);
|
|
1688
1782
|
const colorScheme = useColorScheme(containerDivRef);
|
|
1783
|
+
const gridLineProbeRef = import_react4.useRef(null);
|
|
1689
1784
|
const selectionManager = import_selection_manager.useInitializeSelectionManager({
|
|
1690
1785
|
getNumCols: () => ({ type: "infinity" }),
|
|
1691
1786
|
getNumRows: () => ({ type: "infinity" }),
|
|
@@ -1879,7 +1974,7 @@ var Spreadsheet = import_react4.forwardRef(({
|
|
|
1879
1974
|
if (!ctx)
|
|
1880
1975
|
return;
|
|
1881
1976
|
ctx.clearRect(0, 0, width, height);
|
|
1882
|
-
ctx.strokeStyle = resolveGridLineColor(
|
|
1977
|
+
ctx.strokeStyle = resolveGridLineColor(gridLineProbeRef.current, colorScheme);
|
|
1883
1978
|
ctx.lineWidth = 1;
|
|
1884
1979
|
let x = gridDims.headerWidth;
|
|
1885
1980
|
let colIndex = 0;
|
|
@@ -2642,6 +2737,18 @@ var Spreadsheet = import_react4.forwardRef(({
|
|
|
2642
2737
|
},
|
|
2643
2738
|
tabIndex: 0,
|
|
2644
2739
|
children: [
|
|
2740
|
+
/* @__PURE__ */ jsx_runtime3.jsx("span", {
|
|
2741
|
+
ref: gridLineProbeRef,
|
|
2742
|
+
"aria-hidden": "true",
|
|
2743
|
+
style: {
|
|
2744
|
+
position: "absolute",
|
|
2745
|
+
width: 0,
|
|
2746
|
+
height: 0,
|
|
2747
|
+
visibility: "hidden",
|
|
2748
|
+
pointerEvents: "none",
|
|
2749
|
+
color: "var(--rsp-gridline)"
|
|
2750
|
+
}
|
|
2751
|
+
}),
|
|
2645
2752
|
/* @__PURE__ */ jsx_runtime3.jsx("canvas", {
|
|
2646
2753
|
ref: canvasRef,
|
|
2647
2754
|
className: "rsp-canvas",
|
|
@@ -3554,32 +3661,32 @@ function FormulaSheet({
|
|
|
3554
3661
|
const isLastColumn = cell.colIndex === tableInfo.start.colIndex + tableInfo.headers.size - 1;
|
|
3555
3662
|
const isLastRow = tableInfo.endRow.type === "number" ? cell.rowIndex === tableInfo.endRow.value : false;
|
|
3556
3663
|
const style = {
|
|
3557
|
-
border: "1px solid var(--table-border-light)"
|
|
3664
|
+
border: "1px solid var(--rsp-table-border-light)"
|
|
3558
3665
|
};
|
|
3559
3666
|
if (isHeaderRow) {
|
|
3560
|
-
style.backgroundColor = "var(--table-header-bg)";
|
|
3561
|
-
style.color = "var(--table-header-color)";
|
|
3667
|
+
style.backgroundColor = "var(--rsp-table-header-bg)";
|
|
3668
|
+
style.color = "var(--rsp-table-header-color)";
|
|
3562
3669
|
style.fontWeight = "bold";
|
|
3563
|
-
style.borderBottom = "2px solid var(--table-header-border)";
|
|
3670
|
+
style.borderBottom = "2px solid var(--rsp-table-header-border)";
|
|
3564
3671
|
} else {
|
|
3565
3672
|
const dataRowIndex = cell.rowIndex - tableInfo.start.rowIndex - 1;
|
|
3566
3673
|
if (dataRowIndex % 2 === 0) {
|
|
3567
|
-
style.backgroundColor = "var(--table-even-row-bg)";
|
|
3674
|
+
style.backgroundColor = "var(--rsp-table-even-row-bg)";
|
|
3568
3675
|
} else {
|
|
3569
|
-
style.backgroundColor = "var(--table-odd-row-bg)";
|
|
3676
|
+
style.backgroundColor = "var(--rsp-table-odd-row-bg)";
|
|
3570
3677
|
}
|
|
3571
3678
|
}
|
|
3572
3679
|
if (isFirstColumn) {
|
|
3573
|
-
style.borderLeft = "2px solid var(--table-border-color)";
|
|
3680
|
+
style.borderLeft = "2px solid var(--rsp-table-border-color)";
|
|
3574
3681
|
}
|
|
3575
3682
|
if (isLastColumn) {
|
|
3576
|
-
style.borderRight = "2px solid var(--table-border-color)";
|
|
3683
|
+
style.borderRight = "2px solid var(--rsp-table-border-color)";
|
|
3577
3684
|
}
|
|
3578
3685
|
if (isHeaderRow) {
|
|
3579
|
-
style.borderTop = "2px solid var(--table-border-color)";
|
|
3686
|
+
style.borderTop = "2px solid var(--rsp-table-border-color)";
|
|
3580
3687
|
}
|
|
3581
3688
|
if (isLastRow) {
|
|
3582
|
-
style.borderBottom = "2px solid var(--table-border-color)";
|
|
3689
|
+
style.borderBottom = "2px solid var(--rsp-table-border-color)";
|
|
3583
3690
|
}
|
|
3584
3691
|
if (conditionalStyle?.backgroundColor) {
|
|
3585
3692
|
style.backgroundColor = conditionalStyle.backgroundColor;
|
|
@@ -4042,4 +4149,4 @@ var useClickAway = (ref, callback) => {
|
|
|
4042
4149
|
}, [ref, callback]);
|
|
4043
4150
|
};
|
|
4044
4151
|
|
|
4045
|
-
//# debugId=
|
|
4152
|
+
//# debugId=203CB6768D786E8764756E2164756E21
|