@ricsam/react-spreadsheets 0.0.2 → 0.0.4

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/react-spreadsheets",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "commonjs"
5
5
  }
package/dist/mjs/lib.mjs CHANGED
@@ -69,6 +69,85 @@ function useColorScheme(element) {
69
69
  return scheme;
70
70
  }
71
71
 
72
+ // src/utils/contrast-text.ts
73
+ var DARK_INK = "#101828";
74
+ var LIGHT_INK = "#ffffff";
75
+ var NAMED_COLORS = {
76
+ white: [255, 255, 255],
77
+ black: [0, 0, 0],
78
+ red: [255, 0, 0],
79
+ green: [0, 128, 0],
80
+ blue: [0, 0, 255],
81
+ yellow: [255, 255, 0],
82
+ orange: [255, 165, 0],
83
+ purple: [128, 0, 128],
84
+ gray: [128, 128, 128],
85
+ grey: [128, 128, 128],
86
+ silver: [192, 192, 192]
87
+ };
88
+ var clamp255 = (value) => Math.min(255, Math.max(0, value));
89
+ function parseCssColor(input) {
90
+ const value = input.trim().toLowerCase();
91
+ if (!value || value === "transparent" || value === "currentcolor" || value === "inherit") {
92
+ return;
93
+ }
94
+ if (value.includes("var(") || value.includes("color-mix(") || value.includes("gradient")) {
95
+ return;
96
+ }
97
+ const named = NAMED_COLORS[value];
98
+ if (named) {
99
+ return { r: named[0], g: named[1], b: named[2], a: 1 };
100
+ }
101
+ if (value.startsWith("#")) {
102
+ const hex = value.slice(1);
103
+ const expand = (part) => parseInt(part + part, 16);
104
+ if (hex.length === 3 || hex.length === 4) {
105
+ const r = expand(hex[0]);
106
+ const g = expand(hex[1]);
107
+ const b = expand(hex[2]);
108
+ const a = hex.length === 4 ? expand(hex[3]) / 255 : 1;
109
+ return Number.isNaN(r + g + b) ? undefined : { r, g, b, a };
110
+ }
111
+ if (hex.length === 6 || hex.length === 8) {
112
+ const r = parseInt(hex.slice(0, 2), 16);
113
+ const g = parseInt(hex.slice(2, 4), 16);
114
+ const b = parseInt(hex.slice(4, 6), 16);
115
+ const a = hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1;
116
+ return Number.isNaN(r + g + b) ? undefined : { r, g, b, a };
117
+ }
118
+ return;
119
+ }
120
+ const rgbMatch = value.match(/^rgba?\(([^)]+)\)$/);
121
+ if (rgbMatch) {
122
+ const parts = rgbMatch[1].split(/[\s,/]+/).filter(Boolean);
123
+ if (parts.length < 3)
124
+ return;
125
+ const toChannel = (part) => part.endsWith("%") ? parseFloat(part) / 100 * 255 : parseFloat(part);
126
+ const r = toChannel(parts[0]);
127
+ const g = toChannel(parts[1]);
128
+ const b = toChannel(parts[2]);
129
+ const alphaPart = parts[3];
130
+ const a = alphaPart === undefined ? 1 : alphaPart.endsWith("%") ? parseFloat(alphaPart) / 100 : parseFloat(alphaPart);
131
+ if ([r, g, b, a].some((channel) => Number.isNaN(channel)))
132
+ return;
133
+ return { r: clamp255(r), g: clamp255(g), b: clamp255(b), a };
134
+ }
135
+ return;
136
+ }
137
+ function relativeLuminance({ r, g, b }) {
138
+ const channel = (value) => {
139
+ const srgb = value / 255;
140
+ return srgb <= 0.03928 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);
141
+ };
142
+ return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
143
+ }
144
+ function getContrastingTextColor(background) {
145
+ const color = parseCssColor(background);
146
+ if (!color || color.a < 0.5)
147
+ return;
148
+ return relativeLuminance(color) > 0.45 ? DARK_INK : LIGHT_INK;
149
+ }
150
+
72
151
  // src/utils/cn.ts
73
152
  function cn(...values) {
74
153
  const out = [];
@@ -1247,16 +1326,27 @@ var resizeCellSnapAnchorToNearestEdges = (anchor, rect, handle, columnWidths, ro
1247
1326
 
1248
1327
  // src/spreadsheet/spreadsheet.tsx
1249
1328
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1250
- var resolveGridLineColor = (element, colorScheme) => {
1251
- const fallback = colorScheme === "dark" ? "#4b5563" : "#e1e5e9";
1252
- if (!element || typeof getComputedStyle !== "function") {
1329
+ var resolveGridLineColor = (probe, colorScheme) => {
1330
+ const fallback = colorScheme === "dark" ? "#232c3d" : "#e8ebf1";
1331
+ if (!probe || typeof getComputedStyle !== "function") {
1253
1332
  return fallback;
1254
1333
  }
1255
- const declared = getComputedStyle(element).getPropertyValue("--rsp-gridline").trim();
1256
- if (!declared || declared.includes("light-dark(")) {
1334
+ const resolved = getComputedStyle(probe).color?.trim();
1335
+ if (!resolved || resolved.includes("light-dark(") || resolved === "rgba(0, 0, 0, 0)") {
1257
1336
  return fallback;
1258
1337
  }
1259
- return declared;
1338
+ return resolved;
1339
+ };
1340
+ var withReadableTextColor = (style) => {
1341
+ if (!style?.backgroundColor || style.color)
1342
+ return style;
1343
+ const background = style.backgroundColor;
1344
+ if (typeof background !== "string")
1345
+ return style;
1346
+ const color = getContrastingTextColor(background);
1347
+ if (!color)
1348
+ return style;
1349
+ return { ...style, color };
1260
1350
  };
1261
1351
  var hasBorderStyle = (style) => Boolean(style?.borderColor || style?.borderTopColor || style?.borderRightColor || style?.borderBottomColor || style?.borderLeftColor);
1262
1352
  var CellComponent = React2.memo(({
@@ -1299,7 +1389,7 @@ var CellComponent = React2.memo(({
1299
1389
  isSelected,
1300
1390
  isBeingEdited: isBeingEdited || false
1301
1391
  };
1302
- const customStyle = customCellStyle?.(cellStyleCallbackData);
1392
+ const customStyle = withReadableTextColor(customCellStyle?.(cellStyleCallbackData));
1303
1393
  const canHaveFillHandle = useSelectionManager(selectionManager, () => {
1304
1394
  return selectionManager.canCellHaveFillHandle({
1305
1395
  row: rowIndex,
@@ -1331,7 +1421,8 @@ var CellComponent = React2.memo(({
1331
1421
  top: y,
1332
1422
  width: getColumnWidth(col) - 1,
1333
1423
  height: getRowHeight(row) - 1,
1334
- ...customStyle
1424
+ ...customStyle,
1425
+ ...isBeingEdited ? { backgroundColor: undefined, color: undefined } : null
1335
1426
  },
1336
1427
  children: [
1337
1428
  isBeingEdited ? /* @__PURE__ */ jsx3("textarea", {
@@ -1581,6 +1672,7 @@ var Spreadsheet = forwardRef(({
1581
1672
  const [{ width, height }, containerRef] = useResizeObserver();
1582
1673
  const [containerDivRef, setContainerDivRef] = useState4(null);
1583
1674
  const colorScheme = useColorScheme(containerDivRef);
1675
+ const gridLineProbeRef = useRef3(null);
1584
1676
  const selectionManager = useInitializeSelectionManager({
1585
1677
  getNumCols: () => ({ type: "infinity" }),
1586
1678
  getNumRows: () => ({ type: "infinity" }),
@@ -1774,7 +1866,7 @@ var Spreadsheet = forwardRef(({
1774
1866
  if (!ctx)
1775
1867
  return;
1776
1868
  ctx.clearRect(0, 0, width, height);
1777
- ctx.strokeStyle = resolveGridLineColor(canvasRef.current, colorScheme);
1869
+ ctx.strokeStyle = resolveGridLineColor(gridLineProbeRef.current, colorScheme);
1778
1870
  ctx.lineWidth = 1;
1779
1871
  let x = gridDims.headerWidth;
1780
1872
  let colIndex = 0;
@@ -2522,6 +2614,7 @@ var Spreadsheet = forwardRef(({
2522
2614
  }), [selectionManager]);
2523
2615
  return /* @__PURE__ */ jsx3("div", {
2524
2616
  className: cn("rsp-root", className),
2617
+ "data-rsp-scheme": colorScheme,
2525
2618
  style: { position: "relative", ...style },
2526
2619
  ...containerProps,
2527
2620
  children: /* @__PURE__ */ jsx3("div", {
@@ -2537,6 +2630,18 @@ var Spreadsheet = forwardRef(({
2537
2630
  },
2538
2631
  tabIndex: 0,
2539
2632
  children: [
2633
+ /* @__PURE__ */ jsx3("span", {
2634
+ ref: gridLineProbeRef,
2635
+ "aria-hidden": "true",
2636
+ style: {
2637
+ position: "absolute",
2638
+ width: 0,
2639
+ height: 0,
2640
+ visibility: "hidden",
2641
+ pointerEvents: "none",
2642
+ color: "var(--rsp-gridline)"
2643
+ }
2644
+ }),
2540
2645
  /* @__PURE__ */ jsx3("canvas", {
2541
2646
  ref: canvasRef,
2542
2647
  className: "rsp-canvas",
@@ -2716,7 +2821,15 @@ import {
2716
2821
  parseCellReference as parseCellReference2,
2717
2822
  getCellReference as getCellReference3
2718
2823
  } from "@ricsam/formula-engine";
2719
- import React3, { useCallback as useCallback4, useRef as useRef4, useState as useState5 } from "react";
2824
+ import React3, { useCallback as useCallback4, useRef as useRef4, useState as useState6 } from "react";
2825
+
2826
+ // src/utils/use-scheme-root.ts
2827
+ import { useState as useState5 } from "react";
2828
+ function useSchemeRoot() {
2829
+ const [element, setElement] = useState5(null);
2830
+ const scheme = useColorScheme(element);
2831
+ return { ref: setElement, "data-rsp-scheme": scheme };
2832
+ }
2720
2833
 
2721
2834
  // src/workbook/border-sides.ts
2722
2835
  function normalizeBorderSides(sides, defaultAllWhenUnset = false) {
@@ -3122,7 +3235,7 @@ var MinusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsx4("svg", {
3122
3235
  })
3123
3236
  });
3124
3237
  function useEngine(engine) {
3125
- const [state, setState] = useState5(() => engine.getState());
3238
+ const [state, setState] = useState6(() => engine.getState());
3126
3239
  React3.useEffect(() => {
3127
3240
  return engine.onUpdate(() => {
3128
3241
  setState(engine.getState());
@@ -3227,7 +3340,8 @@ function FormulaSheet({
3227
3340
  showFormulas = false,
3228
3341
  ...rest
3229
3342
  }) {
3230
- const [selectedArea, setSelectedArea] = useState5(null);
3343
+ const schemeRoot = useSchemeRoot();
3344
+ const [selectedArea, setSelectedArea] = useState6(null);
3231
3345
  const clipboardManager = React3.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
3232
3346
  const state = useEngine(engine);
3233
3347
  const sheet = state.workbooks.get(workbookName)?.sheets.get(sheetName);
@@ -3372,6 +3486,7 @@ function FormulaSheet({
3372
3486
  if (!sheet) {
3373
3487
  return /* @__PURE__ */ jsxs3("div", {
3374
3488
  className: "rsp-root rsp-workbook",
3489
+ ...schemeRoot,
3375
3490
  children: [
3376
3491
  toolbar,
3377
3492
  /* @__PURE__ */ jsx4("div", {
@@ -3382,6 +3497,7 @@ function FormulaSheet({
3382
3497
  }
3383
3498
  return /* @__PURE__ */ jsxs3("div", {
3384
3499
  className: "rsp-root rsp-workbook",
3500
+ ...schemeRoot,
3385
3501
  children: [
3386
3502
  toolbar,
3387
3503
  /* @__PURE__ */ jsx4("div", {
@@ -3452,32 +3568,32 @@ function FormulaSheet({
3452
3568
  const isLastColumn = cell.colIndex === tableInfo.start.colIndex + tableInfo.headers.size - 1;
3453
3569
  const isLastRow = tableInfo.endRow.type === "number" ? cell.rowIndex === tableInfo.endRow.value : false;
3454
3570
  const style = {
3455
- border: "1px solid var(--table-border-light)"
3571
+ border: "1px solid var(--rsp-table-border-light)"
3456
3572
  };
3457
3573
  if (isHeaderRow) {
3458
- style.backgroundColor = "var(--table-header-bg)";
3459
- style.color = "var(--table-header-color)";
3574
+ style.backgroundColor = "var(--rsp-table-header-bg)";
3575
+ style.color = "var(--rsp-table-header-color)";
3460
3576
  style.fontWeight = "bold";
3461
- style.borderBottom = "2px solid var(--table-header-border)";
3577
+ style.borderBottom = "2px solid var(--rsp-table-header-border)";
3462
3578
  } else {
3463
3579
  const dataRowIndex = cell.rowIndex - tableInfo.start.rowIndex - 1;
3464
3580
  if (dataRowIndex % 2 === 0) {
3465
- style.backgroundColor = "var(--table-even-row-bg)";
3581
+ style.backgroundColor = "var(--rsp-table-even-row-bg)";
3466
3582
  } else {
3467
- style.backgroundColor = "var(--table-odd-row-bg)";
3583
+ style.backgroundColor = "var(--rsp-table-odd-row-bg)";
3468
3584
  }
3469
3585
  }
3470
3586
  if (isFirstColumn) {
3471
- style.borderLeft = "2px solid var(--table-border-color)";
3587
+ style.borderLeft = "2px solid var(--rsp-table-border-color)";
3472
3588
  }
3473
3589
  if (isLastColumn) {
3474
- style.borderRight = "2px solid var(--table-border-color)";
3590
+ style.borderRight = "2px solid var(--rsp-table-border-color)";
3475
3591
  }
3476
3592
  if (isHeaderRow) {
3477
- style.borderTop = "2px solid var(--table-border-color)";
3593
+ style.borderTop = "2px solid var(--rsp-table-border-color)";
3478
3594
  }
3479
3595
  if (isLastRow) {
3480
- style.borderBottom = "2px solid var(--table-border-color)";
3596
+ style.borderBottom = "2px solid var(--rsp-table-border-color)";
3481
3597
  }
3482
3598
  if (conditionalStyle?.backgroundColor) {
3483
3599
  style.backgroundColor = conditionalStyle.backgroundColor;
@@ -3613,14 +3729,15 @@ function FormulaWorkbook({
3613
3729
  showFormulas = false,
3614
3730
  ...rest
3615
3731
  }) {
3732
+ const schemeRoot = useSchemeRoot();
3616
3733
  const state = useEngine(engine);
3617
- const [renamingSheet, setRenamingSheet] = useState5(null);
3618
- const [newSheetName, setNewSheetName] = useState5("");
3619
- const [spreadsheetZoom, setSpreadsheetZoom] = useState5(1);
3734
+ const [renamingSheet, setRenamingSheet] = useState6(null);
3735
+ const [newSheetName, setNewSheetName] = useState6("");
3736
+ const [spreadsheetZoom, setSpreadsheetZoom] = useState6(1);
3620
3737
  const renameCommittedRef = useRef4(false);
3621
3738
  const clipboardManager = React3.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
3622
3739
  const sheetNames = engine.getOrderedSheetNames(workbookName);
3623
- const [internalActiveSheet, setInternalActiveSheet] = useState5(() => {
3740
+ const [internalActiveSheet, setInternalActiveSheet] = useState6(() => {
3624
3741
  return sheetNames[0] || "";
3625
3742
  });
3626
3743
  const isControlledRef = useRef4(controlledActiveSheet !== undefined);
@@ -3719,6 +3836,7 @@ function FormulaWorkbook({
3719
3836
  };
3720
3837
  return /* @__PURE__ */ jsxs3("div", {
3721
3838
  className: "rsp-root rsp-workbook",
3839
+ ...schemeRoot,
3722
3840
  children: [
3723
3841
  toolbar,
3724
3842
  /* @__PURE__ */ jsxs3("div", {
@@ -3967,6 +4085,7 @@ export {
3967
4085
  getCellReference,
3968
4086
  getCellSnapAnchorFromRect,
3969
4087
  getColumnEdgePosition,
4088
+ getContrastingTextColor,
3970
4089
  getNearestColumnEdgeIndex,
3971
4090
  getNearestRowEdgeIndex,
3972
4091
  getRectFromCellSnapAnchor,
@@ -3980,7 +4099,9 @@ export {
3980
4099
  normalizeCellSnapAnchor,
3981
4100
  onCellEditError,
3982
4101
  parseCellReference,
4102
+ parseCssColor,
3983
4103
  queueCellEditError,
4104
+ relativeLuminance,
3984
4105
  resizeCellSnapAnchorToNearestEdges,
3985
4106
  rowToLetter,
3986
4107
  useClickAway,
@@ -3988,4 +4109,4 @@ export {
3988
4109
  useResizeObserver
3989
4110
  };
3990
4111
 
3991
- //# debugId=FCE294496834A26064756E2164756E21
4112
+ //# debugId=93A3B97B45C8788264756E2164756E21