@ricsam/react-spreadsheets 0.0.12 → 0.0.14

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 CHANGED
@@ -22,6 +22,22 @@ bun add @ricsam/formula-engine
22
22
 
23
23
  `react`, `react-dom` and `@ricsam/selection-manager` are peer dependencies.
24
24
 
25
+ ## Formula rendering and imported defaults
26
+
27
+ `FormulaSheet` shares engine style reads between its cell container and content,
28
+ with a bounded cache invalidated on **every engine update**, including changes to
29
+ other sheets, styles, metadata, undo and redo. Selection-only changes leave
30
+ unchanged cells alone. Consumer style/render callbacks still receive current
31
+ `isSelected` and `isBeingEdited` values; keep callback identities stable (for
32
+ example with `useCallback`) to avoid invalidating all visible cells unnecessarily.
33
+
34
+ Workbook metadata may contain `defaultCellStyle` (a `CellStyle`, optionally with
35
+ `fontFamily: string`). These imported defaults are applied beneath explicit
36
+ engine cell styles. Font sizes use the engine's CSS-pixel convention. False
37
+ bold/italic/underline values override defaults. Without this metadata the existing
38
+ CSS/application defaults remain unchanged; fonts are never inferred from nearby
39
+ cells. Existing saved imports need reimporting to recover missing source defaults.
40
+
25
41
  ## Usage
26
42
 
27
43
  Import the stylesheet once, near your app root:
package/dist/cjs/lib.cjs CHANGED
@@ -3473,13 +3473,21 @@ class WorkbookSelectionManager {
3473
3473
  if (cachedSelections && manager.getState().selections.length === 0) {
3474
3474
  manager.replaceSelections(cachedSelections);
3475
3475
  }
3476
+ let previousFocus = manager.getState().hasFocus;
3477
+ let previousSelections = JSON.stringify(manager.getState().selections);
3476
3478
  const focusCleanup = manager.observeStateChange((state) => state.hasFocus, (hasFocus) => {
3479
+ if (hasFocus === previousFocus)
3480
+ return;
3481
+ previousFocus = hasFocus;
3477
3482
  if (hasFocus && !this.applyingRangeFocus.has(manager)) {
3478
3483
  this.rememberSelections(manager, registeredContext);
3479
3484
  this.emitChange();
3480
3485
  }
3481
3486
  });
3482
3487
  const selectionCleanup = manager.observeStateChange((state) => JSON.stringify(state.selections), (selectionsJson) => {
3488
+ if (selectionsJson === previousSelections)
3489
+ return;
3490
+ previousSelections = selectionsJson;
3483
3491
  if (this.applyingRangeFocus.has(manager))
3484
3492
  return;
3485
3493
  const selections = JSON.parse(selectionsJson);
@@ -4039,6 +4047,186 @@ function FormulaSheet({
4039
4047
  viewId,
4040
4048
  workbookName
4041
4049
  ]);
4050
+ const renderCallbacks = import_react6.useMemo(() => {
4051
+ const metadata = engine.getWorkbookMetadata(workbookName);
4052
+ const defaults = metadata?.defaultCellStyle;
4053
+ const fontStyle = (style) => ({
4054
+ ...style?.fontFamily && { fontFamily: style.fontFamily },
4055
+ ...style?.bold !== undefined && { fontWeight: style.bold ? "bold" : "normal" },
4056
+ ...style?.italic !== undefined && { fontStyle: style.italic ? "italic" : "normal" },
4057
+ ...style?.underline !== undefined && { textDecoration: style.underline ? "underline" : "none" }
4058
+ });
4059
+ const styles = new Map;
4060
+ const getStyle = (cell) => {
4061
+ if (styles.has(cell.id))
4062
+ return styles.get(cell.id);
4063
+ const explicit = engine.getCellStyle({
4064
+ sheetName,
4065
+ workbookName,
4066
+ colIndex: cell.colIndex,
4067
+ rowIndex: cell.rowIndex
4068
+ });
4069
+ const style = defaults ? { ...defaults, ...explicit } : explicit;
4070
+ if (styles.size >= 4096)
4071
+ styles.clear();
4072
+ styles.set(cell.id, style);
4073
+ return style;
4074
+ };
4075
+ return {
4076
+ customCellStyle: (cell) => {
4077
+ const conditionalStyle = getStyle(cell);
4078
+ const tableInfo = engine.isCellInTable({
4079
+ sheetName,
4080
+ colIndex: cell.colIndex,
4081
+ rowIndex: cell.rowIndex,
4082
+ workbookName
4083
+ });
4084
+ if (!tableInfo) {
4085
+ const style = conditionalStyle ? {
4086
+ ...conditionalStyle.backgroundColor && {
4087
+ backgroundColor: conditionalStyle.backgroundColor
4088
+ },
4089
+ ...conditionalStyle.color && {
4090
+ color: conditionalStyle.color
4091
+ },
4092
+ ...conditionalStyle.fontSize && {
4093
+ fontSize: conditionalStyle.fontSize
4094
+ },
4095
+ ...fontStyle(conditionalStyle),
4096
+ ...getBorderCellStyle(conditionalStyle),
4097
+ ...getWrapCellStyle(conditionalStyle)
4098
+ } : {};
4099
+ return customCellStyle ? customCellStyle(cell, style) : style;
4100
+ }
4101
+ const isHeaderRow = cell.rowIndex === tableInfo.start.rowIndex;
4102
+ const isFirstColumn = cell.colIndex === tableInfo.start.colIndex;
4103
+ const isLastColumn = cell.colIndex === tableInfo.start.colIndex + tableInfo.headers.size - 1;
4104
+ const isLastRow = tableInfo.endRow.type === "number" ? cell.rowIndex === tableInfo.endRow.value : false;
4105
+ const style = {
4106
+ border: "1px solid var(--_rsp-table-border-light)"
4107
+ };
4108
+ if (isHeaderRow) {
4109
+ style.backgroundColor = "var(--_rsp-table-header-bg)";
4110
+ style.color = "var(--_rsp-table-header-color)";
4111
+ style.fontWeight = "bold";
4112
+ style.borderBottom = "2px solid var(--_rsp-table-header-border)";
4113
+ } else {
4114
+ const dataRowIndex = cell.rowIndex - tableInfo.start.rowIndex - 1;
4115
+ if (dataRowIndex % 2 === 0) {
4116
+ style.backgroundColor = "var(--_rsp-table-even-row-bg)";
4117
+ } else {
4118
+ style.backgroundColor = "var(--_rsp-table-odd-row-bg)";
4119
+ }
4120
+ }
4121
+ if (isFirstColumn) {
4122
+ style.borderLeft = "2px solid var(--_rsp-table-border-color)";
4123
+ }
4124
+ if (isLastColumn) {
4125
+ style.borderRight = "2px solid var(--_rsp-table-border-color)";
4126
+ }
4127
+ if (isHeaderRow) {
4128
+ style.borderTop = "2px solid var(--_rsp-table-border-color)";
4129
+ }
4130
+ if (isLastRow) {
4131
+ style.borderBottom = "2px solid var(--_rsp-table-border-color)";
4132
+ }
4133
+ if (conditionalStyle?.backgroundColor) {
4134
+ style.backgroundColor = conditionalStyle.backgroundColor;
4135
+ }
4136
+ if (conditionalStyle?.color) {
4137
+ style.color = conditionalStyle.color;
4138
+ }
4139
+ if (conditionalStyle?.fontSize) {
4140
+ style.fontSize = conditionalStyle.fontSize;
4141
+ }
4142
+ Object.assign(style, fontStyle(conditionalStyle));
4143
+ Object.assign(style, getBorderCellStyle(conditionalStyle));
4144
+ Object.assign(style, getWrapCellStyle(conditionalStyle));
4145
+ if (customCellStyle) {
4146
+ return customCellStyle(cell, style);
4147
+ }
4148
+ return style;
4149
+ },
4150
+ customCellRenderer: (cell) => {
4151
+ const cellRef = import_formula_engine2.getCellReference({
4152
+ colIndex: cell.colIndex,
4153
+ rowIndex: cell.rowIndex
4154
+ });
4155
+ const value = showFormulas ? sheet?.content.get(cellRef) : engine.getCellValue({
4156
+ sheetName,
4157
+ workbookName,
4158
+ colIndex: cell.colIndex,
4159
+ rowIndex: cell.rowIndex
4160
+ }, verboseErrors);
4161
+ const conditionalStyle = getStyle(cell);
4162
+ const conditionalStyleProps = {};
4163
+ if (conditionalStyle?.backgroundColor) {
4164
+ conditionalStyleProps.backgroundColor = conditionalStyle.backgroundColor;
4165
+ }
4166
+ if (conditionalStyle?.color) {
4167
+ conditionalStyleProps.color = conditionalStyle.color;
4168
+ }
4169
+ if (conditionalStyle?.fontSize) {
4170
+ conditionalStyleProps.fontSize = conditionalStyle.fontSize;
4171
+ }
4172
+ Object.assign(conditionalStyleProps, fontStyle(conditionalStyle), getWrapContentStyle(conditionalStyle));
4173
+ let defaultElement;
4174
+ if (showFormulas) {
4175
+ defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4176
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4177
+ "data-conditional-text-color": conditionalStyle?.color,
4178
+ style: conditionalStyleProps,
4179
+ children: getCellDisplayText(value)
4180
+ });
4181
+ } else if (typeof value === "boolean") {
4182
+ defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4183
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4184
+ "data-conditional-text-color": conditionalStyle?.color,
4185
+ "data-type": "boolean",
4186
+ style: {
4187
+ ...conditionalStyleProps,
4188
+ width: "100%",
4189
+ textAlign: "center",
4190
+ fontFamily: conditionalStyle?.fontFamily ?? "'Courier New', monospace"
4191
+ },
4192
+ children: value ? "TRUE" : "FALSE"
4193
+ });
4194
+ } else if (typeof value === "number") {
4195
+ defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4196
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4197
+ "data-conditional-text-color": conditionalStyle?.color,
4198
+ "data-type": "number",
4199
+ style: { ...conditionalStyleProps, width: "100%", textAlign: "right" },
4200
+ children: value.toLocaleString("en-US", {
4201
+ minimumFractionDigits: 0,
4202
+ maximumFractionDigits: 10
4203
+ })
4204
+ });
4205
+ } else {
4206
+ defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4207
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4208
+ "data-conditional-text-color": conditionalStyle?.color,
4209
+ style: { ...conditionalStyleProps, width: "100%", textAlign: "left" },
4210
+ children: getCellDisplayText(value)
4211
+ });
4212
+ }
4213
+ if (customCellRenderer) {
4214
+ return customCellRenderer(cell, defaultElement);
4215
+ }
4216
+ return defaultElement;
4217
+ }
4218
+ };
4219
+ }, [
4220
+ engine,
4221
+ state,
4222
+ workbookName,
4223
+ sheetName,
4224
+ sheet,
4225
+ showFormulas,
4226
+ verboseErrors,
4227
+ customCellStyle,
4228
+ customCellRenderer
4229
+ ]);
4042
4230
  if (!sheet) {
4043
4231
  return /* @__PURE__ */ jsx_runtime4.jsxs("div", {
4044
4232
  className: "rsp-root rsp-workbook",
@@ -4080,183 +4268,7 @@ function FormulaSheet({
4080
4268
  containerProps: isSelected && selectedArea ? {
4081
4269
  "data-grid-no-interaction": "true"
4082
4270
  } : {},
4083
- customCellStyle: (cell) => {
4084
- const conditionalStyle = engine.getCellStyle({
4085
- sheetName,
4086
- colIndex: cell.colIndex,
4087
- rowIndex: cell.rowIndex,
4088
- workbookName
4089
- });
4090
- const tableInfo = engine.isCellInTable({
4091
- sheetName,
4092
- colIndex: cell.colIndex,
4093
- rowIndex: cell.rowIndex,
4094
- workbookName
4095
- });
4096
- if (!tableInfo) {
4097
- const style = conditionalStyle ? {
4098
- ...conditionalStyle.backgroundColor && {
4099
- backgroundColor: conditionalStyle.backgroundColor
4100
- },
4101
- ...conditionalStyle.color && {
4102
- color: conditionalStyle.color
4103
- },
4104
- ...conditionalStyle.fontSize && {
4105
- fontSize: conditionalStyle.fontSize
4106
- },
4107
- ...conditionalStyle.bold && {
4108
- fontWeight: "bold"
4109
- },
4110
- ...conditionalStyle.italic && {
4111
- fontStyle: "italic"
4112
- },
4113
- ...conditionalStyle.underline && {
4114
- textDecoration: "underline"
4115
- },
4116
- ...getBorderCellStyle(conditionalStyle),
4117
- ...getWrapCellStyle(conditionalStyle)
4118
- } : {};
4119
- return customCellStyle ? customCellStyle(cell, style) : style;
4120
- }
4121
- const isHeaderRow = cell.rowIndex === tableInfo.start.rowIndex;
4122
- const isFirstColumn = cell.colIndex === tableInfo.start.colIndex;
4123
- const isLastColumn = cell.colIndex === tableInfo.start.colIndex + tableInfo.headers.size - 1;
4124
- const isLastRow = tableInfo.endRow.type === "number" ? cell.rowIndex === tableInfo.endRow.value : false;
4125
- const style = {
4126
- border: "1px solid var(--_rsp-table-border-light)"
4127
- };
4128
- if (isHeaderRow) {
4129
- style.backgroundColor = "var(--_rsp-table-header-bg)";
4130
- style.color = "var(--_rsp-table-header-color)";
4131
- style.fontWeight = "bold";
4132
- style.borderBottom = "2px solid var(--_rsp-table-header-border)";
4133
- } else {
4134
- const dataRowIndex = cell.rowIndex - tableInfo.start.rowIndex - 1;
4135
- if (dataRowIndex % 2 === 0) {
4136
- style.backgroundColor = "var(--_rsp-table-even-row-bg)";
4137
- } else {
4138
- style.backgroundColor = "var(--_rsp-table-odd-row-bg)";
4139
- }
4140
- }
4141
- if (isFirstColumn) {
4142
- style.borderLeft = "2px solid var(--_rsp-table-border-color)";
4143
- }
4144
- if (isLastColumn) {
4145
- style.borderRight = "2px solid var(--_rsp-table-border-color)";
4146
- }
4147
- if (isHeaderRow) {
4148
- style.borderTop = "2px solid var(--_rsp-table-border-color)";
4149
- }
4150
- if (isLastRow) {
4151
- style.borderBottom = "2px solid var(--_rsp-table-border-color)";
4152
- }
4153
- if (conditionalStyle?.backgroundColor) {
4154
- style.backgroundColor = conditionalStyle.backgroundColor;
4155
- }
4156
- if (conditionalStyle?.color) {
4157
- style.color = conditionalStyle.color;
4158
- }
4159
- if (conditionalStyle?.fontSize) {
4160
- style.fontSize = conditionalStyle.fontSize;
4161
- }
4162
- if (conditionalStyle?.bold) {
4163
- style.fontWeight = "bold";
4164
- }
4165
- if (conditionalStyle?.italic) {
4166
- style.fontStyle = "italic";
4167
- }
4168
- if (conditionalStyle?.underline) {
4169
- style.textDecoration = "underline";
4170
- }
4171
- Object.assign(style, getBorderCellStyle(conditionalStyle));
4172
- Object.assign(style, getWrapCellStyle(conditionalStyle));
4173
- if (customCellStyle) {
4174
- return customCellStyle(cell, style);
4175
- }
4176
- return style;
4177
- },
4178
- customCellRenderer: (cell) => {
4179
- const cellRef = import_formula_engine2.getCellReference({
4180
- colIndex: cell.colIndex,
4181
- rowIndex: cell.rowIndex
4182
- });
4183
- const value = showFormulas ? sheet?.content.get(cellRef) : engine.getCellValue({
4184
- sheetName,
4185
- workbookName,
4186
- colIndex: cell.colIndex,
4187
- rowIndex: cell.rowIndex
4188
- }, verboseErrors);
4189
- const conditionalStyle = engine.getCellStyle({
4190
- sheetName,
4191
- colIndex: cell.colIndex,
4192
- rowIndex: cell.rowIndex,
4193
- workbookName
4194
- });
4195
- const conditionalStyleProps = {};
4196
- if (conditionalStyle?.backgroundColor) {
4197
- conditionalStyleProps.backgroundColor = conditionalStyle.backgroundColor;
4198
- }
4199
- if (conditionalStyle?.color) {
4200
- conditionalStyleProps.color = conditionalStyle.color;
4201
- }
4202
- if (conditionalStyle?.fontSize) {
4203
- conditionalStyleProps.fontSize = conditionalStyle.fontSize;
4204
- }
4205
- if (conditionalStyle?.bold) {
4206
- conditionalStyleProps.fontWeight = "bold";
4207
- }
4208
- if (conditionalStyle?.italic) {
4209
- conditionalStyleProps.fontStyle = "italic";
4210
- }
4211
- if (conditionalStyle?.underline) {
4212
- conditionalStyleProps.textDecoration = "underline";
4213
- }
4214
- Object.assign(conditionalStyleProps, getWrapContentStyle(conditionalStyle));
4215
- let defaultElement;
4216
- if (showFormulas) {
4217
- defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4218
- "data-conditional-background": conditionalStyle?.backgroundColor,
4219
- "data-conditional-text-color": conditionalStyle?.color,
4220
- style: conditionalStyleProps,
4221
- children: getCellDisplayText(value)
4222
- });
4223
- } else if (typeof value === "boolean") {
4224
- defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4225
- "data-conditional-background": conditionalStyle?.backgroundColor,
4226
- "data-conditional-text-color": conditionalStyle?.color,
4227
- "data-type": "boolean",
4228
- style: {
4229
- ...conditionalStyleProps,
4230
- width: "100%",
4231
- textAlign: "center",
4232
- fontFamily: "'Courier New', monospace"
4233
- },
4234
- children: value ? "TRUE" : "FALSE"
4235
- });
4236
- } else if (typeof value === "number") {
4237
- defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4238
- "data-conditional-background": conditionalStyle?.backgroundColor,
4239
- "data-conditional-text-color": conditionalStyle?.color,
4240
- "data-type": "number",
4241
- style: { ...conditionalStyleProps, width: "100%", textAlign: "right" },
4242
- children: value.toLocaleString("en-US", {
4243
- minimumFractionDigits: 0,
4244
- maximumFractionDigits: 10
4245
- })
4246
- });
4247
- } else {
4248
- defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4249
- "data-conditional-background": conditionalStyle?.backgroundColor,
4250
- "data-conditional-text-color": conditionalStyle?.color,
4251
- style: { ...conditionalStyleProps, width: "100%", textAlign: "left" },
4252
- children: getCellDisplayText(value)
4253
- });
4254
- }
4255
- if (customCellRenderer) {
4256
- return customCellRenderer(cell, defaultElement);
4257
- }
4258
- return defaultElement;
4259
- }
4271
+ ...renderCallbacks
4260
4272
  })
4261
4273
  })
4262
4274
  ]
@@ -4615,4 +4627,4 @@ var useClickAway = (ref, callback) => {
4615
4627
  }, [ref, callback]);
4616
4628
  };
4617
4629
 
4618
- //# debugId=95BAABDC3CCBBC2364756E2164756E21
4630
+ //# debugId=07CD4F6A4866444B64756E2164756E21