@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.
- package/README.md +39 -6
- package/dist/cjs/lib.cjs +163 -42
- package/dist/cjs/lib.cjs.map +7 -5
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/lib.mjs +148 -27
- package/dist/mjs/lib.mjs.map +7 -5
- package/dist/mjs/package.json +1 -1
- package/dist/styles.css +290 -71
- package/dist/types/lib.d.ts +1 -0
- package/dist/types/utils/contrast-text.d.ts +36 -0
- package/dist/types/utils/use-scheme-root.d.ts +16 -0
- package/package.json +2 -2
package/dist/mjs/package.json
CHANGED
package/dist/styles.css
CHANGED
|
@@ -4,45 +4,138 @@
|
|
|
4
4
|
* Import once in your app:
|
|
5
5
|
* import "@ricsam/react-spreadsheets/styles.css";
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* ---------------------------------------------------------------------------
|
|
8
|
+
* Theming
|
|
9
|
+
* ---------------------------------------------------------------------------
|
|
10
|
+
* Every visual is driven by `--rsp-*` custom properties. Because the tokens are
|
|
11
|
+
* declared on `:root` (not on `.rsp-root`), you can override them from *any*
|
|
12
|
+
* ancestor and the grid will pick them up:
|
|
9
13
|
*
|
|
10
|
-
* .
|
|
14
|
+
* .my-panel { --rsp-accent: #7c3aed; --rsp-cell-font-size: 13px; }
|
|
11
15
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
16
|
+
* ---------------------------------------------------------------------------
|
|
17
|
+
* Light / dark
|
|
18
|
+
* ---------------------------------------------------------------------------
|
|
19
|
+
* By default the grid follows the user's OS preference. To pin a subtree, set
|
|
20
|
+
* `color-scheme` on the grid root or on any ancestor:
|
|
21
|
+
*
|
|
22
|
+
* .workbook-stage { color-scheme: light; } // always light
|
|
23
|
+
* .workbook-stage { color-scheme: dark; } // always dark
|
|
24
|
+
*
|
|
25
|
+
* Or use the helper classes shipped below:
|
|
26
|
+
*
|
|
27
|
+
* <FormulaWorkbook className="rsp-theme-light" />
|
|
28
|
+
* <FormulaWorkbook className="rsp-theme-dark" />
|
|
29
|
+
*
|
|
30
|
+
* Implementation note — why not `light-dark()`?
|
|
31
|
+
* ---------------------------------------------------------------------------
|
|
32
|
+
* `light-dark()` would be the natural choice, but bundlers that target
|
|
33
|
+
* browsers without native support (Vite's Lightning CSS, for example) lower it
|
|
34
|
+
* to a pair of switch custom properties that are only emitted in rules which
|
|
35
|
+
* *also* declare `color-scheme`. Tokens declared once on a shared root then
|
|
36
|
+
* compute to an invalid value like `#ffffff #0f1523`, and every themed surface
|
|
37
|
+
* silently falls back to `transparent` / inherited text — a failure that only
|
|
38
|
+
* appears in a production build.
|
|
39
|
+
*
|
|
40
|
+
* Instead the same space-toggle trick is applied directly, using our own
|
|
41
|
+
* `--rsp-light` / `--rsp-dark` switches. Exactly one of the two is set to
|
|
42
|
+
* `initial` (making its `var()` fall back to the color) while the other is set
|
|
43
|
+
* to a single space (contributing nothing), so each token resolves to one
|
|
44
|
+
* color. This is plain custom-property substitution that no bundler rewrites,
|
|
45
|
+
* and it inherits, so an ancestor's pin still wins.
|
|
46
|
+
*
|
|
47
|
+
* A plain stylesheet cannot read an ancestor's `color-scheme`, so the component
|
|
48
|
+
* resolves it from the mounted element and reflects the result onto the grid
|
|
49
|
+
* root as `data-rsp-scheme`. That covers a pin declared in CSS, inline, or via
|
|
50
|
+
* the helper classes. The media query below is the pre-resolution default.
|
|
14
51
|
*/
|
|
15
52
|
|
|
53
|
+
/* Default: follow the OS preference. */
|
|
54
|
+
:root,
|
|
16
55
|
.rsp-root {
|
|
17
|
-
|
|
56
|
+
--rsp-light: initial;
|
|
57
|
+
--rsp-dark: ;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@media (prefers-color-scheme: dark) {
|
|
61
|
+
:root,
|
|
62
|
+
.rsp-root {
|
|
63
|
+
--rsp-light: ;
|
|
64
|
+
--rsp-dark: initial;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Resolved by the component from the inherited `color-scheme`, so a pin set in
|
|
69
|
+
CSS, inline, or via the helper classes below is all honoured. */
|
|
70
|
+
[data-rsp-scheme="light"] {
|
|
71
|
+
--rsp-light: initial;
|
|
72
|
+
--rsp-dark: ;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
[data-rsp-scheme="dark"] {
|
|
76
|
+
--rsp-light: ;
|
|
77
|
+
--rsp-dark: initial;
|
|
78
|
+
}
|
|
18
79
|
|
|
80
|
+
/* Explicit opt-in pins. `color-scheme` is set too so native scrollbars and
|
|
81
|
+
form controls agree with the token switch. */
|
|
82
|
+
.rsp-theme-light {
|
|
83
|
+
color-scheme: light;
|
|
84
|
+
--rsp-light: initial;
|
|
85
|
+
--rsp-dark: ;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.rsp-theme-dark {
|
|
89
|
+
color-scheme: dark;
|
|
90
|
+
--rsp-light: ;
|
|
91
|
+
--rsp-dark: initial;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
:root,
|
|
95
|
+
.rsp-root {
|
|
19
96
|
/* Surfaces */
|
|
20
|
-
--rsp-bg: light-dark
|
|
21
|
-
--rsp-
|
|
22
|
-
--rsp-
|
|
23
|
-
--rsp-
|
|
97
|
+
--rsp-bg: var(--rsp-light, #ffffff) var(--rsp-dark, #0f1523);
|
|
98
|
+
--rsp-surface: var(--rsp-light, #ffffff) var(--rsp-dark, #151c2c);
|
|
99
|
+
--rsp-header-bg: var(--rsp-light, #f7f8fa) var(--rsp-dark, #1a2233);
|
|
100
|
+
--rsp-header-active-bg: var(--rsp-light, #e9edf5) var(--rsp-dark, #24304a);
|
|
101
|
+
--rsp-corner-bg: var(--rsp-light, #eef1f6) var(--rsp-dark, #1a2233);
|
|
102
|
+
--rsp-editor-bg: var(--rsp-light, #ffffff) var(--rsp-dark, #1a2233);
|
|
24
103
|
|
|
25
104
|
/* Lines */
|
|
26
|
-
--rsp-border: light-dark
|
|
27
|
-
--rsp-corner-border: light-dark
|
|
28
|
-
--rsp-gridline: light-dark
|
|
105
|
+
--rsp-border: var(--rsp-light, #e3e7ee) var(--rsp-dark, #2a3346);
|
|
106
|
+
--rsp-corner-border: var(--rsp-light, #d7dce6) var(--rsp-dark, #2a3346);
|
|
107
|
+
--rsp-gridline: var(--rsp-light, #e8ebf1) var(--rsp-dark, #232c3d);
|
|
29
108
|
|
|
30
109
|
/* Text */
|
|
31
|
-
--rsp-text: light-dark
|
|
32
|
-
--rsp-header-text: light-dark
|
|
33
|
-
--rsp-muted-text: light-dark
|
|
34
|
-
|
|
35
|
-
/* Accent / selection
|
|
36
|
-
|
|
110
|
+
--rsp-text: var(--rsp-light, #101828) var(--rsp-dark, #e8ecf4);
|
|
111
|
+
--rsp-header-text: var(--rsp-light, #5a6478) var(--rsp-dark, #99a4bb);
|
|
112
|
+
--rsp-muted-text: var(--rsp-light, #6b7280) var(--rsp-dark, #8c97ad);
|
|
113
|
+
|
|
114
|
+
/* Accent / selection.
|
|
115
|
+
NOTE: `@ricsam/selection-manager` paints the selection *perimeter* as an
|
|
116
|
+
inline box-shadow using a hardcoded `#2196F3`, which CSS cannot recolor.
|
|
117
|
+
The default accent stays in that blue family so the perimeter agrees with
|
|
118
|
+
the rest of the chrome out of the box. Overriding `--rsp-accent` with a
|
|
119
|
+
distant hue will make the perimeter read slightly differently. */
|
|
120
|
+
--rsp-accent: var(--rsp-light, #2196f3) var(--rsp-dark, #58a6ff);
|
|
37
121
|
--rsp-accent-contrast: #ffffff;
|
|
38
|
-
--rsp-selection-bg: light-dark
|
|
39
|
-
--rsp-selection-range-bg: light-dark
|
|
40
|
-
--rsp-hover-bg: light-dark
|
|
41
|
-
--rsp-focus-ring: light
|
|
122
|
+
--rsp-selection-bg: var(--rsp-light, #e8f2fe) var(--rsp-dark, #17304f);
|
|
123
|
+
--rsp-selection-range-bg: var(--rsp-light, #f1f7fe) var(--rsp-dark, #14263d);
|
|
124
|
+
--rsp-hover-bg: var(--rsp-light, #f5f7fa) var(--rsp-dark, #1c2436);
|
|
125
|
+
--rsp-focus-ring: var(--rsp-light, rgba(33, 150, 243, 0.22)) var(--rsp-dark, rgba(88, 166, 255, 0.28));
|
|
42
126
|
|
|
43
127
|
/* Danger (delete buttons, invalid input) */
|
|
44
|
-
--rsp-danger: light-dark
|
|
45
|
-
--rsp-danger-bg: light-dark
|
|
128
|
+
--rsp-danger: var(--rsp-light, #d92d20) var(--rsp-dark, #ff8a80);
|
|
129
|
+
--rsp-danger-bg: var(--rsp-light, #fef3f2) var(--rsp-dark, #3a1512);
|
|
130
|
+
|
|
131
|
+
/* Tables (consumed by the workbook's inline cell styles) */
|
|
132
|
+
--rsp-table-header-bg: var(--rsp-light, #2196f3) var(--rsp-dark, #1c3a5e);
|
|
133
|
+
--rsp-table-header-color: var(--rsp-light, #ffffff) var(--rsp-dark, #e8ecf4);
|
|
134
|
+
--rsp-table-header-border: var(--rsp-light, #1877c9) var(--rsp-dark, #2a5384);
|
|
135
|
+
--rsp-table-even-row-bg: var(--rsp-light, #f7f9fc) var(--rsp-dark, #161e2e);
|
|
136
|
+
--rsp-table-odd-row-bg: var(--rsp-light, #ffffff) var(--rsp-dark, #131a28);
|
|
137
|
+
--rsp-table-border-color: var(--rsp-light, #2196f3) var(--rsp-dark, #34405c);
|
|
138
|
+
--rsp-table-border-light: var(--rsp-light, #dfe4ec) var(--rsp-dark, #2a3346);
|
|
46
139
|
|
|
47
140
|
/* Typography */
|
|
48
141
|
--rsp-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
@@ -50,15 +143,42 @@
|
|
|
50
143
|
--rsp-mono-font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo,
|
|
51
144
|
Consolas, "Liberation Mono", monospace;
|
|
52
145
|
--rsp-cell-font-size: 12px;
|
|
53
|
-
--rsp-header-font-size:
|
|
146
|
+
--rsp-header-font-size: 11px;
|
|
54
147
|
|
|
55
|
-
/* Shape */
|
|
56
|
-
--rsp-radius:
|
|
148
|
+
/* Shape + motion */
|
|
149
|
+
--rsp-radius: 8px;
|
|
150
|
+
--rsp-radius-sm: 5px;
|
|
151
|
+
--rsp-cell-padding: 0 7px;
|
|
152
|
+
--rsp-transition: 120ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
57
153
|
|
|
58
154
|
/* Fill handle */
|
|
59
155
|
--rsp-fill-handle-size: 8px;
|
|
60
156
|
}
|
|
61
157
|
|
|
158
|
+
/* Opt-in scheme pins. Applied to `.rsp-root` (or any ancestor) these force one
|
|
159
|
+
mode regardless of the OS preference, and every `light-dark()` token below
|
|
160
|
+
follows because `color-scheme` inherits. */
|
|
161
|
+
.rsp-theme-light {
|
|
162
|
+
color-scheme: light;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.rsp-theme-dark {
|
|
166
|
+
color-scheme: dark;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.rsp-root {
|
|
170
|
+
/* No `color-scheme` here on purpose: declaring it would override a pin set by
|
|
171
|
+
an ancestor. See the file header. */
|
|
172
|
+
color: var(--rsp-text);
|
|
173
|
+
font-family: var(--rsp-font-family);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.rsp-root *,
|
|
177
|
+
.rsp-root *::before,
|
|
178
|
+
.rsp-root *::after {
|
|
179
|
+
box-sizing: border-box;
|
|
180
|
+
}
|
|
181
|
+
|
|
62
182
|
/* ------------------------------------------------------------------ *
|
|
63
183
|
* Spreadsheet grid
|
|
64
184
|
* ------------------------------------------------------------------ */
|
|
@@ -77,7 +197,7 @@
|
|
|
77
197
|
}
|
|
78
198
|
|
|
79
199
|
.rsp-container.rsp-focused {
|
|
80
|
-
box-shadow: 0 0 0
|
|
200
|
+
box-shadow: inset 0 0 0 1px var(--rsp-accent);
|
|
81
201
|
}
|
|
82
202
|
|
|
83
203
|
.rsp-canvas,
|
|
@@ -92,9 +212,10 @@
|
|
|
92
212
|
position: absolute;
|
|
93
213
|
border: 1px solid transparent;
|
|
94
214
|
background: transparent;
|
|
95
|
-
padding:
|
|
215
|
+
padding: var(--rsp-cell-padding);
|
|
96
216
|
font-size: var(--rsp-cell-font-size);
|
|
97
217
|
font-family: var(--rsp-font-family);
|
|
218
|
+
font-variant-numeric: tabular-nums;
|
|
98
219
|
overflow: hidden;
|
|
99
220
|
white-space: pre;
|
|
100
221
|
text-overflow: ellipsis;
|
|
@@ -108,30 +229,47 @@
|
|
|
108
229
|
|
|
109
230
|
.rsp-cell.rsp-cell-multiline {
|
|
110
231
|
align-items: flex-start;
|
|
232
|
+
padding-top: 4px;
|
|
111
233
|
}
|
|
112
234
|
|
|
113
|
-
|
|
235
|
+
/* Hover tint must never repaint a cell the consumer has colored, so it is
|
|
236
|
+
restricted to "plain" cells (no custom background). */
|
|
237
|
+
.rsp-cell.rsp-cell-plain:hover {
|
|
114
238
|
background: var(--rsp-hover-bg);
|
|
115
239
|
}
|
|
116
240
|
|
|
117
|
-
.rsp-cell.rsp-cell-selected {
|
|
118
|
-
border-color: var(--rsp-accent);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
241
|
/* `rsp-cell-plain` marks cells whose background is not overridden by a
|
|
122
242
|
consumer-supplied custom style, so selection tinting is safe to apply. */
|
|
243
|
+
.rsp-cell.rsp-cell-in-selection.rsp-cell-plain {
|
|
244
|
+
background: var(--rsp-selection-range-bg);
|
|
245
|
+
}
|
|
246
|
+
|
|
123
247
|
.rsp-cell.rsp-cell-selected.rsp-cell-plain {
|
|
124
248
|
background: var(--rsp-selection-bg);
|
|
125
249
|
}
|
|
126
250
|
|
|
127
|
-
|
|
128
|
-
|
|
251
|
+
/* The selection perimeter is painted by the selection manager as an inline
|
|
252
|
+
`box-shadow` on the individual edge cells, so this rule must NOT declare a
|
|
253
|
+
box-shadow of its own: `rsp-cell-selected` is present on every cell of a
|
|
254
|
+
multi-cell range, and a full outline here would box each interior cell.
|
|
255
|
+
Only the stacking order is raised so the perimeter is not clipped by
|
|
256
|
+
neighbouring cells. */
|
|
257
|
+
.rsp-cell.rsp-cell-selected {
|
|
258
|
+
z-index: 2;
|
|
129
259
|
}
|
|
130
260
|
|
|
131
|
-
|
|
261
|
+
/* The editor surface must win over the selection tint above (equal specificity
|
|
262
|
+
would otherwise leave the selected-cell background showing through), and over
|
|
263
|
+
a consumer background so the text being typed stays legible. */
|
|
264
|
+
.rsp-cell.rsp-cell-editing,
|
|
265
|
+
.rsp-cell.rsp-cell-editing.rsp-cell-plain,
|
|
266
|
+
.rsp-cell.rsp-cell-selected.rsp-cell-editing,
|
|
267
|
+
.rsp-cell.rsp-cell-selected.rsp-cell-editing.rsp-cell-plain {
|
|
132
268
|
background: var(--rsp-editor-bg);
|
|
133
|
-
|
|
134
|
-
box-shadow:
|
|
269
|
+
color: var(--rsp-text);
|
|
270
|
+
box-shadow:
|
|
271
|
+
inset 0 0 0 2px var(--rsp-accent),
|
|
272
|
+
0 6px 16px -4px var(--rsp-focus-ring);
|
|
135
273
|
z-index: 999;
|
|
136
274
|
pointer-events: none;
|
|
137
275
|
}
|
|
@@ -148,6 +286,7 @@
|
|
|
148
286
|
font-family: var(--rsp-font-family);
|
|
149
287
|
background: transparent;
|
|
150
288
|
color: var(--rsp-text);
|
|
289
|
+
caret-color: var(--rsp-accent);
|
|
151
290
|
resize: none;
|
|
152
291
|
overflow: hidden;
|
|
153
292
|
z-index: 1000;
|
|
@@ -157,12 +296,15 @@
|
|
|
157
296
|
|
|
158
297
|
.rsp-fill-handle {
|
|
159
298
|
position: absolute;
|
|
160
|
-
bottom:
|
|
161
|
-
right:
|
|
299
|
+
bottom: -1px;
|
|
300
|
+
right: -1px;
|
|
162
301
|
width: var(--rsp-fill-handle-size);
|
|
163
302
|
height: var(--rsp-fill-handle-size);
|
|
164
303
|
background-color: var(--rsp-accent);
|
|
304
|
+
border: 1px solid var(--rsp-bg);
|
|
305
|
+
border-radius: 1px;
|
|
165
306
|
cursor: crosshair;
|
|
307
|
+
z-index: 3;
|
|
166
308
|
}
|
|
167
309
|
|
|
168
310
|
/* Headers ---------------------------------------------------------- */
|
|
@@ -175,14 +317,34 @@
|
|
|
175
317
|
align-items: center;
|
|
176
318
|
justify-content: center;
|
|
177
319
|
font-size: var(--rsp-header-font-size);
|
|
320
|
+
font-weight: 500;
|
|
321
|
+
letter-spacing: 0.02em;
|
|
178
322
|
cursor: pointer;
|
|
179
323
|
user-select: none;
|
|
180
324
|
color: var(--rsp-header-text);
|
|
181
325
|
box-sizing: border-box;
|
|
326
|
+
transition: background-color var(--rsp-transition), color var(--rsp-transition);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.rsp-header:hover {
|
|
330
|
+
background-color: var(--rsp-header-active-bg);
|
|
331
|
+
color: var(--rsp-text);
|
|
182
332
|
}
|
|
183
333
|
|
|
184
334
|
.rsp-header.rsp-header-selected {
|
|
185
|
-
background-color: var(--rsp-
|
|
335
|
+
background-color: var(--rsp-header-active-bg);
|
|
336
|
+
color: var(--rsp-accent);
|
|
337
|
+
font-weight: 600;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* A crisp accent edge on the side that faces the grid marks the active
|
|
341
|
+
row/column without changing the header's box metrics. */
|
|
342
|
+
.rsp-header.rsp-header-col.rsp-header-selected {
|
|
343
|
+
box-shadow: inset 0 -2px 0 var(--rsp-accent);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.rsp-header.rsp-header-row.rsp-header-selected {
|
|
347
|
+
box-shadow: inset -2px 0 0 var(--rsp-accent);
|
|
186
348
|
}
|
|
187
349
|
|
|
188
350
|
.rsp-column-headers,
|
|
@@ -231,6 +393,11 @@
|
|
|
231
393
|
z-index: 10;
|
|
232
394
|
}
|
|
233
395
|
|
|
396
|
+
.rsp-col-resize-handle:hover,
|
|
397
|
+
.rsp-row-resize-handle:hover {
|
|
398
|
+
background: var(--rsp-accent);
|
|
399
|
+
}
|
|
400
|
+
|
|
234
401
|
/* Overlays --------------------------------------------------------- */
|
|
235
402
|
|
|
236
403
|
.rsp-overlays {
|
|
@@ -249,7 +416,7 @@
|
|
|
249
416
|
opacity: 0.5;
|
|
250
417
|
pointer-events: none;
|
|
251
418
|
border: 2px dashed var(--rsp-accent);
|
|
252
|
-
border-radius:
|
|
419
|
+
border-radius: var(--rsp-radius-sm);
|
|
253
420
|
background-color: var(--rsp-focus-ring);
|
|
254
421
|
}
|
|
255
422
|
|
|
@@ -283,7 +450,7 @@
|
|
|
283
450
|
display: flex;
|
|
284
451
|
align-items: center;
|
|
285
452
|
gap: 8px;
|
|
286
|
-
padding:
|
|
453
|
+
padding: 7px 10px;
|
|
287
454
|
border-bottom: 1px solid var(--rsp-border);
|
|
288
455
|
background: var(--rsp-header-bg);
|
|
289
456
|
flex: none;
|
|
@@ -291,8 +458,9 @@
|
|
|
291
458
|
|
|
292
459
|
.rsp-workbook-name {
|
|
293
460
|
font-weight: 600;
|
|
294
|
-
font-size:
|
|
295
|
-
|
|
461
|
+
font-size: 12px;
|
|
462
|
+
letter-spacing: -0.01em;
|
|
463
|
+
color: var(--rsp-text);
|
|
296
464
|
white-space: nowrap;
|
|
297
465
|
}
|
|
298
466
|
|
|
@@ -305,8 +473,8 @@
|
|
|
305
473
|
.rsp-sheet-tabs {
|
|
306
474
|
display: flex;
|
|
307
475
|
align-items: center;
|
|
308
|
-
gap:
|
|
309
|
-
padding:
|
|
476
|
+
gap: 6px;
|
|
477
|
+
padding: 6px 10px;
|
|
310
478
|
border-top: 1px solid var(--rsp-border);
|
|
311
479
|
background: var(--rsp-header-bg);
|
|
312
480
|
flex: none;
|
|
@@ -324,27 +492,38 @@
|
|
|
324
492
|
display: inline-flex;
|
|
325
493
|
align-items: center;
|
|
326
494
|
gap: 6px;
|
|
327
|
-
padding:
|
|
495
|
+
padding: 5px 11px;
|
|
328
496
|
border: 1px solid transparent;
|
|
329
|
-
border-radius: var(--rsp-radius);
|
|
497
|
+
border-radius: var(--rsp-radius-sm);
|
|
330
498
|
font-size: 12px;
|
|
331
499
|
line-height: 1.4;
|
|
332
500
|
cursor: pointer;
|
|
333
501
|
white-space: nowrap;
|
|
334
502
|
color: var(--rsp-header-text);
|
|
335
503
|
background: transparent;
|
|
504
|
+
transition: background-color var(--rsp-transition), color var(--rsp-transition);
|
|
336
505
|
}
|
|
337
506
|
|
|
338
507
|
.rsp-sheet-tab:hover {
|
|
339
508
|
background: var(--rsp-hover-bg);
|
|
509
|
+
color: var(--rsp-text);
|
|
340
510
|
}
|
|
341
511
|
|
|
342
512
|
.rsp-sheet-tab.rsp-sheet-tab-active {
|
|
343
|
-
background: var(--rsp-
|
|
344
|
-
border-color:
|
|
345
|
-
color: var(--rsp-
|
|
513
|
+
background: var(--rsp-accent);
|
|
514
|
+
border-color: transparent;
|
|
515
|
+
color: var(--rsp-accent-contrast);
|
|
346
516
|
font-weight: 600;
|
|
347
|
-
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* Icon buttons inside the active tab must read against the accent fill. */
|
|
520
|
+
.rsp-sheet-tab.rsp-sheet-tab-active .rsp-icon-button {
|
|
521
|
+
color: var(--rsp-accent-contrast);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.rsp-sheet-tab.rsp-sheet-tab-active .rsp-icon-button:hover {
|
|
525
|
+
background: rgba(255, 255, 255, 0.22);
|
|
526
|
+
color: var(--rsp-accent-contrast);
|
|
348
527
|
}
|
|
349
528
|
|
|
350
529
|
/* Per-tab action buttons reveal on hover / when the tab is active. */
|
|
@@ -353,7 +532,7 @@
|
|
|
353
532
|
align-items: center;
|
|
354
533
|
gap: 2px;
|
|
355
534
|
opacity: 0;
|
|
356
|
-
transition: opacity
|
|
535
|
+
transition: opacity var(--rsp-transition);
|
|
357
536
|
}
|
|
358
537
|
|
|
359
538
|
.rsp-sheet-tab:hover .sheet-tab-actions,
|
|
@@ -364,12 +543,14 @@
|
|
|
364
543
|
|
|
365
544
|
.rsp-sheet-tab-input {
|
|
366
545
|
font: inherit;
|
|
367
|
-
color:
|
|
546
|
+
color: var(--rsp-text);
|
|
368
547
|
background: var(--rsp-editor-bg);
|
|
369
548
|
border: 1px solid var(--rsp-accent);
|
|
370
|
-
border-radius:
|
|
371
|
-
padding:
|
|
549
|
+
border-radius: var(--rsp-radius-sm);
|
|
550
|
+
padding: 2px 6px;
|
|
372
551
|
min-width: 80px;
|
|
552
|
+
outline: none;
|
|
553
|
+
box-shadow: 0 0 0 3px var(--rsp-focus-ring);
|
|
373
554
|
}
|
|
374
555
|
|
|
375
556
|
.rsp-icon-button {
|
|
@@ -380,12 +561,13 @@
|
|
|
380
561
|
height: 22px;
|
|
381
562
|
padding: 0;
|
|
382
563
|
border: none;
|
|
383
|
-
border-radius:
|
|
564
|
+
border-radius: var(--rsp-radius-sm);
|
|
384
565
|
background: transparent;
|
|
385
566
|
color: var(--rsp-muted-text);
|
|
386
567
|
cursor: pointer;
|
|
387
568
|
font-size: 14px;
|
|
388
569
|
line-height: 1;
|
|
570
|
+
transition: background-color var(--rsp-transition), color var(--rsp-transition);
|
|
389
571
|
}
|
|
390
572
|
|
|
391
573
|
.rsp-icon-button:hover {
|
|
@@ -393,6 +575,11 @@
|
|
|
393
575
|
color: var(--rsp-text);
|
|
394
576
|
}
|
|
395
577
|
|
|
578
|
+
.rsp-icon-button:disabled {
|
|
579
|
+
opacity: 0.4;
|
|
580
|
+
cursor: not-allowed;
|
|
581
|
+
}
|
|
582
|
+
|
|
396
583
|
.rsp-icon-button.rsp-icon-button-delete:hover {
|
|
397
584
|
background: var(--rsp-danger-bg);
|
|
398
585
|
color: var(--rsp-danger);
|
|
@@ -403,19 +590,21 @@
|
|
|
403
590
|
align-items: center;
|
|
404
591
|
justify-content: center;
|
|
405
592
|
gap: 4px;
|
|
406
|
-
height:
|
|
407
|
-
padding: 0
|
|
593
|
+
height: 26px;
|
|
594
|
+
padding: 0 11px;
|
|
408
595
|
border: 1px dashed var(--rsp-border);
|
|
409
|
-
border-radius: var(--rsp-radius);
|
|
596
|
+
border-radius: var(--rsp-radius-sm);
|
|
410
597
|
background: transparent;
|
|
411
598
|
color: var(--rsp-header-text);
|
|
412
599
|
font-size: 12px;
|
|
413
600
|
cursor: pointer;
|
|
414
601
|
white-space: nowrap;
|
|
602
|
+
transition: border-color var(--rsp-transition), color var(--rsp-transition);
|
|
415
603
|
}
|
|
416
604
|
|
|
417
605
|
.rsp-add-sheet-button:hover {
|
|
418
606
|
border-color: var(--rsp-accent);
|
|
607
|
+
border-style: solid;
|
|
419
608
|
color: var(--rsp-accent);
|
|
420
609
|
}
|
|
421
610
|
|
|
@@ -430,7 +619,7 @@
|
|
|
430
619
|
.rsp-zoom-value {
|
|
431
620
|
min-width: 44px;
|
|
432
621
|
text-align: center;
|
|
433
|
-
font-size:
|
|
622
|
+
font-size: 11px;
|
|
434
623
|
color: var(--rsp-muted-text);
|
|
435
624
|
font-variant-numeric: tabular-nums;
|
|
436
625
|
}
|
|
@@ -441,7 +630,7 @@
|
|
|
441
630
|
display: flex;
|
|
442
631
|
align-items: center;
|
|
443
632
|
gap: 8px;
|
|
444
|
-
padding:
|
|
633
|
+
padding: 6px 10px;
|
|
445
634
|
border-bottom: 1px solid var(--rsp-border);
|
|
446
635
|
background: var(--rsp-bg);
|
|
447
636
|
flex: none;
|
|
@@ -449,12 +638,12 @@
|
|
|
449
638
|
|
|
450
639
|
.rsp-name-box {
|
|
451
640
|
min-width: 84px;
|
|
452
|
-
padding:
|
|
641
|
+
padding: 4px 8px;
|
|
453
642
|
border: 1px solid var(--rsp-border);
|
|
454
|
-
border-radius:
|
|
643
|
+
border-radius: var(--rsp-radius-sm);
|
|
455
644
|
background: var(--rsp-header-bg);
|
|
456
645
|
font-family: var(--rsp-mono-font-family);
|
|
457
|
-
font-size:
|
|
646
|
+
font-size: 11px;
|
|
458
647
|
text-align: center;
|
|
459
648
|
color: var(--rsp-text);
|
|
460
649
|
}
|
|
@@ -462,22 +651,52 @@
|
|
|
462
651
|
.rsp-formula-input {
|
|
463
652
|
flex: 1;
|
|
464
653
|
min-width: 0;
|
|
465
|
-
padding:
|
|
654
|
+
padding: 4px 9px;
|
|
466
655
|
border: 1px solid var(--rsp-border);
|
|
467
|
-
border-radius:
|
|
656
|
+
border-radius: var(--rsp-radius-sm);
|
|
468
657
|
background: var(--rsp-editor-bg);
|
|
469
658
|
color: var(--rsp-text);
|
|
470
659
|
font-family: var(--rsp-mono-font-family);
|
|
471
660
|
font-size: 12px;
|
|
661
|
+
transition: border-color var(--rsp-transition), box-shadow var(--rsp-transition);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.rsp-formula-input::placeholder {
|
|
665
|
+
color: var(--rsp-muted-text);
|
|
472
666
|
}
|
|
473
667
|
|
|
474
668
|
.rsp-formula-input:focus {
|
|
475
669
|
outline: none;
|
|
476
670
|
border-color: var(--rsp-accent);
|
|
477
|
-
box-shadow: 0 0 0
|
|
671
|
+
box-shadow: 0 0 0 3px var(--rsp-focus-ring);
|
|
478
672
|
}
|
|
479
673
|
|
|
480
674
|
.rsp-formula-input:disabled {
|
|
481
675
|
opacity: 0.6;
|
|
482
676
|
cursor: not-allowed;
|
|
483
677
|
}
|
|
678
|
+
|
|
679
|
+
/* Scrollbars ------------------------------------------------------- */
|
|
680
|
+
|
|
681
|
+
.rsp-root {
|
|
682
|
+
scrollbar-color: var(--rsp-border) transparent;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.rsp-root ::-webkit-scrollbar {
|
|
686
|
+
width: 10px;
|
|
687
|
+
height: 10px;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.rsp-root ::-webkit-scrollbar-track {
|
|
691
|
+
background: transparent;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.rsp-root ::-webkit-scrollbar-thumb {
|
|
695
|
+
background: var(--rsp-border);
|
|
696
|
+
border: 2px solid var(--rsp-bg);
|
|
697
|
+
border-radius: 99px;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
.rsp-root ::-webkit-scrollbar-thumb:hover {
|
|
701
|
+
background: var(--rsp-muted-text);
|
|
702
|
+
}
|
package/dist/types/lib.d.ts
CHANGED
|
@@ -23,4 +23,5 @@ export { cn } from "./utils/cn";
|
|
|
23
23
|
export { useResizeObserver } from "./utils/use-resize-observer";
|
|
24
24
|
export { useColorScheme, type ColorScheme } from "./utils/use-color-scheme";
|
|
25
25
|
export { useClickAway } from "./utils/use-click-away";
|
|
26
|
+
export { getContrastingTextColor, parseCssColor, relativeLuminance, } from "./utils/contrast-text";
|
|
26
27
|
export { ViewportStream, type ViewportState, type ResizeHandle } from "./grid/types";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contrast helpers for consumer-supplied cell backgrounds.
|
|
3
|
+
*
|
|
4
|
+
* The grid's default text color is theme-aware (`--rsp-text` resolves through
|
|
5
|
+
* `light-dark()`), but a consumer-supplied `backgroundColor` usually is not —
|
|
6
|
+
* spreadsheet fills typically come from a document model that stores one
|
|
7
|
+
* literal color, e.g. `#f1f5f9`.
|
|
8
|
+
*
|
|
9
|
+
* Without help, a light literal fill combined with the dark theme's near-white
|
|
10
|
+
* text renders as white-on-white and the cell becomes unreadable. These helpers
|
|
11
|
+
* pick a readable ink for a known background instead.
|
|
12
|
+
*
|
|
13
|
+
* Only values we can actually parse are considered. Anything dynamic
|
|
14
|
+
* (`var(...)`, `color-mix(...)`, gradients, `transparent`, unknown keywords)
|
|
15
|
+
* returns `undefined` so the caller keeps the inherited themed color.
|
|
16
|
+
*/
|
|
17
|
+
type Rgba = {
|
|
18
|
+
r: number;
|
|
19
|
+
g: number;
|
|
20
|
+
b: number;
|
|
21
|
+
a: number;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Parses hex (#rgb, #rgba, #rrggbb, #rrggbbaa), rgb()/rgba() and a small set of
|
|
25
|
+
* common keywords. Returns `undefined` for anything else.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseCssColor(input: string): Rgba | undefined;
|
|
28
|
+
/** Relative luminance per WCAG 2.1. */
|
|
29
|
+
export declare function relativeLuminance({ r, g, b }: Pick<Rgba, "r" | "g" | "b">): number;
|
|
30
|
+
/**
|
|
31
|
+
* Returns a readable ink for `background`, or `undefined` when the background
|
|
32
|
+
* is unparseable or too translucent to classify (in which case the themed
|
|
33
|
+
* default should keep applying).
|
|
34
|
+
*/
|
|
35
|
+
export declare function getContrastingTextColor(background: string): string | undefined;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ColorScheme } from "./use-color-scheme";
|
|
2
|
+
/**
|
|
3
|
+
* Props for a `.rsp-root` element so its light/dark tokens match the inherited
|
|
4
|
+
* `color-scheme`.
|
|
5
|
+
*
|
|
6
|
+
* A stylesheet cannot branch on an *ancestor's* `color-scheme`, and the
|
|
7
|
+
* `light-dark()` function is rewritten by some bundlers into switch variables
|
|
8
|
+
* that only exist next to a `color-scheme` declaration (see `styles.css`).
|
|
9
|
+
* Resolving the scheme from the mounted element and reflecting it as
|
|
10
|
+
* `data-rsp-scheme` keeps the tokens correct without the component declaring
|
|
11
|
+
* `color-scheme` itself, which would override a consumer's pin.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useSchemeRoot(): {
|
|
14
|
+
ref: (element: HTMLElement | null) => void;
|
|
15
|
+
"data-rsp-scheme": ColorScheme;
|
|
16
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/react-spreadsheets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Headless-friendly React spreadsheet and workbook components: infinite virtualized grid, zoom, resizing, clipboard, fill handles and formula-engine bindings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/mjs/lib.mjs",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"build": "NODE_ENV=production bun build-lib.ts",
|
|
22
22
|
"test": "bun test",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
|
-
"demo": "bun
|
|
24
|
+
"demo": "bun demo/serve.ts"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
27
27
|
"react",
|