chart-factory 0.1.2
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/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/chart-factory-table.js +1400 -0
- package/dist/chart-factory.css +2368 -0
- package/dist/chart-factory.js +11398 -0
- package/dist/chart-factory.min.js +7 -0
- package/docs/API.md +1815 -0
- package/docs/chart-guide.md +872 -0
- package/docs/color-guide.md +134 -0
- package/docs/conformance-matrix.md +864 -0
- package/docs/theming.md +107 -0
- package/index.d.ts +10453 -0
- package/package.json +83 -0
- package/src/core/base.css +508 -0
- package/src/core/builder-metadata.generated.mjs +406 -0
- package/src/core/d3-base.js +249 -0
- package/src/core/d3-chart-factory.js +18547 -0
- package/src/core/d3-data.js +0 -0
- package/src/core/d3-suggest.js +565 -0
- package/src/core/example-nav.css +62 -0
- package/src/core/example-nav.js +249 -0
- package/src/core/palettes.js +310 -0
- package/src/core/theme-toggle.js +126 -0
- package/src/core/tokens.css +792 -0
- package/src/table/d3-table.js +1570 -0
- package/src/table/table.css +899 -0
- package/src/table/tokens.css +157 -0
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D3Table Component Styles
|
|
3
|
+
*
|
|
4
|
+
* Import with core and table tokens:
|
|
5
|
+
*
|
|
6
|
+
* @import 'chart-factory/core/tokens.css';
|
|
7
|
+
* @import 'chart-factory/core/base.css';
|
|
8
|
+
* @import 'chart-factory/table/tokens.css';
|
|
9
|
+
* @import 'chart-factory/table/table.css';
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/* ===========================
|
|
13
|
+
TABLE CONTAINER (extends .d3-container)
|
|
14
|
+
=========================== */
|
|
15
|
+
|
|
16
|
+
.d3-table-container {
|
|
17
|
+
display: inline-block;
|
|
18
|
+
background: var(--color-bg-container);
|
|
19
|
+
padding: var(--space-8);
|
|
20
|
+
box-shadow: var(--shadow-container);
|
|
21
|
+
text-align: left;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Wrapper for table + footer ensures footer matches table width */
|
|
25
|
+
.d3-table-wrapper {
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Fill layout: the wrapper must be block-level so the scroll container
|
|
31
|
+
is bounded by the page container, not shrink-wrapped to the table.
|
|
32
|
+
(The footer still matches the table — it's 100% width too.)
|
|
33
|
+
Applied by D3Table.prepareDom(). */
|
|
34
|
+
.d3-table-wrapper--fill {
|
|
35
|
+
display: block;
|
|
36
|
+
width: 100%;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.d3-table-title {
|
|
40
|
+
font-family: var(--font-family);
|
|
41
|
+
font-size: var(--font-size-title);
|
|
42
|
+
font-weight: var(--font-weight-bold);
|
|
43
|
+
margin: 0 0 var(--space-2) 0;
|
|
44
|
+
color: var(--color-black);
|
|
45
|
+
letter-spacing: var(--letter-spacing-tight);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.d3-table-subtitle {
|
|
49
|
+
font-family: var(--font-family);
|
|
50
|
+
font-size: var(--font-size-subtitle);
|
|
51
|
+
color: var(--color-text-subtle);
|
|
52
|
+
margin: 0 0 var(--space-6) 0;
|
|
53
|
+
line-height: var(--line-height-normal);
|
|
54
|
+
font-weight: var(--font-weight-normal);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.d3-table-footer {
|
|
58
|
+
margin-top: 0;
|
|
59
|
+
padding-top: var(--space-4);
|
|
60
|
+
border-top: 1px solid var(--color-border-footer);
|
|
61
|
+
font-family: var(--font-family);
|
|
62
|
+
font-size: var(--font-size-xs);
|
|
63
|
+
color: var(--color-text-faint);
|
|
64
|
+
text-transform: uppercase;
|
|
65
|
+
letter-spacing: var(--letter-spacing-wider);
|
|
66
|
+
font-weight: var(--font-weight-semibold);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ===========================
|
|
70
|
+
TABLE BASE
|
|
71
|
+
=========================== */
|
|
72
|
+
|
|
73
|
+
table {
|
|
74
|
+
width: auto;
|
|
75
|
+
border-collapse: collapse;
|
|
76
|
+
font-family: var(--font-family);
|
|
77
|
+
font-size: var(--table-font-size);
|
|
78
|
+
margin-top: var(--space-3);
|
|
79
|
+
margin-bottom: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* ===========================
|
|
83
|
+
FILL LAYOUT (default)
|
|
84
|
+
The table fills its container; headers and text cells wrap;
|
|
85
|
+
numeric cells never wrap. Below the JS-computed min-width
|
|
86
|
+
floor, .d3-table-scroll takes over with horizontal scroll.
|
|
87
|
+
=========================== */
|
|
88
|
+
|
|
89
|
+
.d3-table-scroll {
|
|
90
|
+
overflow-x: auto;
|
|
91
|
+
max-width: 100%;
|
|
92
|
+
-webkit-overflow-scrolling: touch;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
table.d3-table--fill {
|
|
96
|
+
/* Fit content, up to the container, never below the computed floor:
|
|
97
|
+
columns take their natural widths and leftover card width stays
|
|
98
|
+
OUTSIDE the table as plain whitespace instead of pooling inside a
|
|
99
|
+
stretched column. Bar columns then grow floor -> cap from measured
|
|
100
|
+
container slack (adjustBarColumnFlex). */
|
|
101
|
+
width: fit-content;
|
|
102
|
+
max-width: 100%;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
table.d3-table--fill th {
|
|
106
|
+
white-space: normal;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
table.d3-table--fill td {
|
|
110
|
+
white-space: normal;
|
|
111
|
+
overflow-wrap: break-word;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
table.d3-table--fill td.align-right,
|
|
115
|
+
table.d3-table--fill td.align-center,
|
|
116
|
+
table.d3-table--fill td.numeric-cell,
|
|
117
|
+
table.d3-table--fill td.secondary-cell,
|
|
118
|
+
table.d3-table--fill td.rank-cell {
|
|
119
|
+
white-space: nowrap;
|
|
120
|
+
overflow-wrap: normal;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ===========================
|
|
124
|
+
HEADER
|
|
125
|
+
=========================== */
|
|
126
|
+
|
|
127
|
+
thead {
|
|
128
|
+
border-bottom: var(--table-header-border);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
th {
|
|
132
|
+
text-align: left;
|
|
133
|
+
padding: var(--table-header-padding-y) var(--table-cell-padding-x) var(--table-header-padding-bottom) var(--table-cell-padding-near);
|
|
134
|
+
font-weight: var(--font-weight-normal);
|
|
135
|
+
font-size: var(--table-header-font-size);
|
|
136
|
+
text-transform: uppercase;
|
|
137
|
+
letter-spacing: var(--letter-spacing-wide);
|
|
138
|
+
color: var(--color-text-muted);
|
|
139
|
+
white-space: nowrap;
|
|
140
|
+
vertical-align: baseline;
|
|
141
|
+
box-sizing: border-box;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
th:first-child {
|
|
145
|
+
padding-left: 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
th:last-child {
|
|
149
|
+
padding-right: 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
th.align-right {
|
|
153
|
+
text-align: right;
|
|
154
|
+
padding: var(--table-header-padding-y) var(--table-cell-padding-near) var(--table-header-padding-bottom) var(--table-cell-padding-x);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
th.align-center {
|
|
158
|
+
text-align: center;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* ===========================
|
|
162
|
+
BODY
|
|
163
|
+
=========================== */
|
|
164
|
+
|
|
165
|
+
tbody tr {
|
|
166
|
+
border-bottom: var(--table-row-border);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
tbody tr:last-child {
|
|
170
|
+
border-bottom: none;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
tbody tr:hover {
|
|
174
|
+
background-color: var(--color-bg-hover);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
td {
|
|
178
|
+
padding: var(--table-cell-padding-y) var(--table-cell-padding-x) var(--table-cell-padding-y) var(--table-cell-padding-near);
|
|
179
|
+
color: var(--color-text-primary);
|
|
180
|
+
font-weight: var(--font-weight-normal);
|
|
181
|
+
height: var(--table-cell-height);
|
|
182
|
+
box-sizing: border-box;
|
|
183
|
+
vertical-align: middle;
|
|
184
|
+
white-space: nowrap;
|
|
185
|
+
overflow: hidden;
|
|
186
|
+
text-overflow: ellipsis;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
td:first-child {
|
|
190
|
+
padding-left: 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
td:last-child {
|
|
194
|
+
padding-right: 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
td.align-right {
|
|
198
|
+
text-align: right;
|
|
199
|
+
padding: var(--table-cell-padding-y) var(--table-cell-padding-near) var(--table-cell-padding-y) var(--table-cell-padding-x);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
td.align-center {
|
|
203
|
+
text-align: center;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* ===========================
|
|
207
|
+
CELL TYPES
|
|
208
|
+
=========================== */
|
|
209
|
+
|
|
210
|
+
/*
|
|
211
|
+
* Primary cells - bold, larger font
|
|
212
|
+
* Use for: names, titles, key identifiers
|
|
213
|
+
* Generic: primary-cell | Legacy: team-cell
|
|
214
|
+
*/
|
|
215
|
+
td.primary-cell,
|
|
216
|
+
td.team-cell {
|
|
217
|
+
font-family: var(--table-primary-font, inherit);
|
|
218
|
+
font-weight: var(--font-weight-bold);
|
|
219
|
+
font-size: var(--table-primary-font-size, var(--table-team-font-size));
|
|
220
|
+
color: var(--color-black);
|
|
221
|
+
white-space: normal;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/*
|
|
225
|
+
* Secondary cells - subdued, smaller
|
|
226
|
+
* Use for: ranks, indices, row numbers
|
|
227
|
+
* Generic: secondary-cell | Legacy: rank-cell
|
|
228
|
+
*/
|
|
229
|
+
td.secondary-cell,
|
|
230
|
+
td.rank-cell {
|
|
231
|
+
color: var(--color-text-disabled);
|
|
232
|
+
font-weight: var(--font-weight-semibold);
|
|
233
|
+
font-size: var(--table-secondary-font-size, var(--table-rank-font-size));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
th.auto-width.secondary-cell,
|
|
237
|
+
td.auto-width.secondary-cell,
|
|
238
|
+
th.auto-width.rank-cell,
|
|
239
|
+
td.auto-width.rank-cell {
|
|
240
|
+
/* Compact padding for narrow rank/index columns */
|
|
241
|
+
padding-left: 0;
|
|
242
|
+
padding-right: var(--space-2);
|
|
243
|
+
min-width: var(--col-secondary-min, var(--col-rank-min));
|
|
244
|
+
max-width: var(--col-secondary-max, var(--col-rank-max));
|
|
245
|
+
text-align: left;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
/*
|
|
250
|
+
* Muted cells - de-emphasized text
|
|
251
|
+
* Use for: supplementary info, metadata
|
|
252
|
+
*/
|
|
253
|
+
td.muted-cell {
|
|
254
|
+
color: var(--color-text-disabled);
|
|
255
|
+
font-size: var(--table-font-size);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/*
|
|
259
|
+
* Numeric cells - right-aligned, tabular figures
|
|
260
|
+
* Use for: numbers, statistics, amounts
|
|
261
|
+
*/
|
|
262
|
+
td.numeric-cell {
|
|
263
|
+
text-align: right;
|
|
264
|
+
font-variant-numeric: tabular-nums;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* ===========================
|
|
268
|
+
SUBTITLE CELLS
|
|
269
|
+
=========================== */
|
|
270
|
+
|
|
271
|
+
td.has-subtitle {
|
|
272
|
+
height: auto;
|
|
273
|
+
padding-top: var(--space-2);
|
|
274
|
+
padding-bottom: var(--space-2);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.cell-with-subtitle {
|
|
278
|
+
display: flex;
|
|
279
|
+
flex-direction: column;
|
|
280
|
+
gap: 1px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.cell-main-text {
|
|
284
|
+
font-weight: var(--font-weight-bold);
|
|
285
|
+
font-size: var(--table-primary-font-size);
|
|
286
|
+
color: var(--color-black);
|
|
287
|
+
line-height: 1.2;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.cell-subtitle-text {
|
|
291
|
+
font-weight: var(--font-weight-normal);
|
|
292
|
+
font-size: var(--font-size-md);
|
|
293
|
+
color: var(--color-text-disabled);
|
|
294
|
+
line-height: 1.2;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* ===========================
|
|
298
|
+
STICKY HEADER / STICKY FIRST COLUMN
|
|
299
|
+
=========================== */
|
|
300
|
+
|
|
301
|
+
/* Sticky header. border-collapse drops borders on stuck cells, so the
|
|
302
|
+
header's bottom rule is redrawn as an inset shadow (on the last
|
|
303
|
+
header row only — a group row above it carries no rule). */
|
|
304
|
+
table.d3-table--sticky-header thead th {
|
|
305
|
+
position: sticky;
|
|
306
|
+
top: 0;
|
|
307
|
+
z-index: 2;
|
|
308
|
+
background: var(--table-sticky-bg, #fff);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
table.d3-table--sticky-header thead tr:last-child th {
|
|
312
|
+
box-shadow: inset 0 -2px 0 var(--color-black, #000);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
table.d3-table--sticky-header thead {
|
|
316
|
+
border-bottom: none;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* In-wrapper vertical scrolling (config.maxHeight) */
|
|
320
|
+
.d3-table-scroll--vertical {
|
|
321
|
+
overflow-y: auto;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Floating header: sticky emulation on horizontally-scrolling tables.
|
|
325
|
+
JS translates the thead with the page scroll (position: sticky can't
|
|
326
|
+
escape the horizontal scroll container). will-change lives ONLY on the
|
|
327
|
+
floating state: on the base thead it would create a z-index:auto
|
|
328
|
+
stacking context that traps the th's z-index, letting tbody bar fills
|
|
329
|
+
and heatmap chips paint over the page-stuck header. */
|
|
330
|
+
table.d3-table--sticky-header thead.d3-table-thead--floating {
|
|
331
|
+
position: relative;
|
|
332
|
+
z-index: 2;
|
|
333
|
+
will-change: transform;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
table.d3-table--sticky-header thead.d3-table-thead--floating th {
|
|
337
|
+
background: var(--table-sticky-bg, #fff);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* Page-level sticky: overflow-x clip does not create a scroll container,
|
|
341
|
+
applied by JS only while the table fits its container horizontally */
|
|
342
|
+
.d3-table-scroll--clip {
|
|
343
|
+
overflow-x: clip;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* Sticky first column */
|
|
347
|
+
table.d3-table--sticky-first-col th:first-child,
|
|
348
|
+
table.d3-table--sticky-first-col td:first-child {
|
|
349
|
+
position: sticky;
|
|
350
|
+
left: 0;
|
|
351
|
+
z-index: 1;
|
|
352
|
+
background: var(--table-sticky-bg, #fff);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
table.d3-table--sticky-first-col thead th:first-child {
|
|
356
|
+
z-index: 3;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
table.d3-table--sticky-first-col tbody tr:hover td:first-child {
|
|
360
|
+
background-color: var(--color-bg-hover);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* Edge shadow appears only once the container is actually scrolled */
|
|
364
|
+
.d3-table-scroll.is-h-scrolled table.d3-table--sticky-first-col th:first-child,
|
|
365
|
+
.d3-table-scroll.is-h-scrolled table.d3-table--sticky-first-col td:first-child {
|
|
366
|
+
box-shadow: var(--table-sticky-edge-shadow);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* Both stickies on one table: the corner header cell needs both shadows */
|
|
370
|
+
table.d3-table--sticky-header.d3-table--sticky-first-col thead tr:last-child th:first-child {
|
|
371
|
+
box-shadow: inset 0 -2px 0 var(--color-black, #000);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.d3-table-scroll.is-h-scrolled table.d3-table--sticky-header.d3-table--sticky-first-col thead tr:last-child th:first-child {
|
|
375
|
+
box-shadow: inset 0 -2px 0 var(--color-black, #000), var(--table-sticky-edge-shadow);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/* ===========================
|
|
379
|
+
COLUMN GROUP HEADERS
|
|
380
|
+
=========================== */
|
|
381
|
+
|
|
382
|
+
.d3-table-group-row th {
|
|
383
|
+
padding-bottom: var(--space-1);
|
|
384
|
+
vertical-align: bottom;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.d3-table-group-row th.group-header {
|
|
388
|
+
text-align: center;
|
|
389
|
+
color: var(--color-text-muted);
|
|
390
|
+
/* The rule is a bottom-anchored background (not border-bottom) so it
|
|
391
|
+
can be inset from the cell edges — adjacent groups get a visible
|
|
392
|
+
gap that marks where one group ends and the next begins. */
|
|
393
|
+
background-image: linear-gradient(
|
|
394
|
+
var(--table-group-rule-color, var(--color-border-row, #e5e7eb)),
|
|
395
|
+
var(--table-group-rule-color, var(--color-border-row, #e5e7eb)));
|
|
396
|
+
background-repeat: no-repeat;
|
|
397
|
+
background-position: bottom center;
|
|
398
|
+
background-size: calc(100% - 2 * var(--table-group-rule-inset, 10px)) var(--table-group-rule-width, 1px);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/* ===========================
|
|
402
|
+
ROW CAP (maxRows + Show all)
|
|
403
|
+
=========================== */
|
|
404
|
+
|
|
405
|
+
tr.row-capped {
|
|
406
|
+
display: none !important;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.d3-table-showmore {
|
|
410
|
+
display: block;
|
|
411
|
+
background: none;
|
|
412
|
+
border: 0;
|
|
413
|
+
padding: var(--space-2) 0;
|
|
414
|
+
margin-top: var(--space-1);
|
|
415
|
+
font-family: var(--font-family);
|
|
416
|
+
font-size: var(--table-header-font-size);
|
|
417
|
+
text-transform: uppercase;
|
|
418
|
+
letter-spacing: var(--letter-spacing-wide);
|
|
419
|
+
color: var(--color-text-muted);
|
|
420
|
+
cursor: pointer;
|
|
421
|
+
text-align: left;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.d3-table-showmore:hover {
|
|
425
|
+
color: var(--color-text-primary, var(--color-black));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.d3-table-showmore:focus-visible {
|
|
429
|
+
outline: 2px solid var(--chart-primary, #3b82f6);
|
|
430
|
+
outline-offset: 2px;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/* ===========================
|
|
434
|
+
FOOTER / TOTAL ROW
|
|
435
|
+
=========================== */
|
|
436
|
+
|
|
437
|
+
tfoot td {
|
|
438
|
+
font-weight: var(--font-weight-bold);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* First summary row carries the heavy total rule; later rows (e.g. a
|
|
442
|
+
per-game average under the totals) separate with the normal border */
|
|
443
|
+
tfoot tr:first-child td {
|
|
444
|
+
border-top: var(--table-total-border);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
tfoot tr + tr td {
|
|
448
|
+
border-top: var(--table-row-border);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/* ===========================
|
|
452
|
+
EXPANDABLE SUB-ROWS
|
|
453
|
+
=========================== */
|
|
454
|
+
|
|
455
|
+
/* Chevron toggle at the start of an expandable parent row's first cell.
|
|
456
|
+
Same drawn-border chevron as sort indicators: points right collapsed,
|
|
457
|
+
rotates to point down when expanded. */
|
|
458
|
+
.row-toggle {
|
|
459
|
+
background: none;
|
|
460
|
+
border: 0;
|
|
461
|
+
padding: 0;
|
|
462
|
+
margin-right: var(--space-2);
|
|
463
|
+
width: var(--table-toggle-width, 14px);
|
|
464
|
+
cursor: pointer;
|
|
465
|
+
color: var(--color-text-muted);
|
|
466
|
+
display: inline-block;
|
|
467
|
+
vertical-align: middle;
|
|
468
|
+
line-height: 0;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.row-toggle::after {
|
|
472
|
+
content: '';
|
|
473
|
+
display: inline-block;
|
|
474
|
+
width: var(--sort-chevron-size, 5px);
|
|
475
|
+
height: var(--sort-chevron-size, 5px);
|
|
476
|
+
border-right: var(--sort-chevron-stroke, 1.5px) solid currentColor;
|
|
477
|
+
border-bottom: var(--sort-chevron-stroke, 1.5px) solid currentColor;
|
|
478
|
+
transform: rotate(-45deg);
|
|
479
|
+
transition: transform var(--sort-transition-duration) ease;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.row-toggle:hover {
|
|
483
|
+
color: var(--color-text-primary, var(--color-black));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.row-toggle:focus-visible {
|
|
487
|
+
outline: 2px solid var(--chart-primary, #3b82f6);
|
|
488
|
+
outline-offset: 2px;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
tr.expandable-row.expanded .row-toggle::after {
|
|
492
|
+
transform: rotate(45deg);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
tr.expandable-row td {
|
|
496
|
+
cursor: default;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/* Same-width spacer keeps childless parent rows aligned with toggled ones */
|
|
500
|
+
.row-toggle-spacer {
|
|
501
|
+
display: inline-block;
|
|
502
|
+
width: var(--table-toggle-width, 14px);
|
|
503
|
+
margin-right: var(--space-2);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/* Sub-rows: indented under the parent, quieter type */
|
|
507
|
+
tr.sub-row td:first-child {
|
|
508
|
+
padding-left: var(--table-subrow-indent, 22px);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
tr.sub-row td.primary-cell,
|
|
512
|
+
tr.sub-row td.team-cell {
|
|
513
|
+
font-size: var(--table-font-size);
|
|
514
|
+
font-weight: var(--font-weight-semibold);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/* ===========================
|
|
518
|
+
IMAGE CELLS (logos / flags / headshots)
|
|
519
|
+
=========================== */
|
|
520
|
+
|
|
521
|
+
.cell-with-image {
|
|
522
|
+
display: flex;
|
|
523
|
+
align-items: center;
|
|
524
|
+
gap: var(--table-image-gap);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
td.align-right .cell-with-image {
|
|
528
|
+
justify-content: flex-end;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
td.align-center .cell-with-image {
|
|
532
|
+
justify-content: center;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.d3-table-img {
|
|
536
|
+
width: var(--table-image-size);
|
|
537
|
+
height: var(--table-image-size);
|
|
538
|
+
flex-shrink: 0;
|
|
539
|
+
display: block;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
img.d3-table-img.img-fit-cover { object-fit: cover; }
|
|
543
|
+
img.d3-table-img.img-fit-contain { object-fit: contain; }
|
|
544
|
+
|
|
545
|
+
.d3-table-img.img-circle { border-radius: 50%; }
|
|
546
|
+
.d3-table-img.img-rounded { border-radius: var(--table-image-radius); }
|
|
547
|
+
.d3-table-img.img-square { border-radius: 0; }
|
|
548
|
+
|
|
549
|
+
/* Rows without an image keep an invisible slot so text stays aligned */
|
|
550
|
+
.d3-table-img-placeholder {
|
|
551
|
+
visibility: hidden;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/* ===========================
|
|
555
|
+
AUTO WIDTH UTILITY
|
|
556
|
+
=========================== */
|
|
557
|
+
|
|
558
|
+
.auto-width {
|
|
559
|
+
white-space: nowrap;
|
|
560
|
+
/* width handled by JavaScript */
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/* Opt-in wrapping for long text columns (column config `wrap: true`).
|
|
564
|
+
Beats the auto-width nowrap; pair with a column maxWidth (applied as an
|
|
565
|
+
inline style) to control where the wrap engages. Data cells only —
|
|
566
|
+
headers keep their single-line layout. */
|
|
567
|
+
td.wrap-cell,
|
|
568
|
+
td.auto-width.wrap-cell {
|
|
569
|
+
white-space: normal;
|
|
570
|
+
overflow-wrap: break-word;
|
|
571
|
+
line-height: var(--table-wrap-line-height, 1.35);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/* ===========================
|
|
575
|
+
BAR CHART CELLS
|
|
576
|
+
=========================== */
|
|
577
|
+
|
|
578
|
+
td.bar-cell {
|
|
579
|
+
position: relative;
|
|
580
|
+
min-width: var(--bar-column-min-width);
|
|
581
|
+
overflow: visible;
|
|
582
|
+
padding: var(--table-cell-padding-y) var(--table-cell-padding-x);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
th.bar-cell {
|
|
586
|
+
text-align: center;
|
|
587
|
+
padding: var(--table-header-padding-y) var(--table-cell-padding-x) var(--table-header-padding-bottom) var(--table-cell-padding-x);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.bar-background {
|
|
591
|
+
position: absolute;
|
|
592
|
+
top: var(--bar-margin-y);
|
|
593
|
+
left: var(--table-cell-padding-x);
|
|
594
|
+
bottom: var(--bar-margin-y);
|
|
595
|
+
height: auto;
|
|
596
|
+
opacity: var(--bar-opacity);
|
|
597
|
+
z-index: 0;
|
|
598
|
+
border-radius: var(--table-bar-border-radius, 2px);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/* Positioning context spanning the cell's padded interior: bar widths and
|
|
602
|
+
label positions are percentages OF THE TRACK, so they can never bleed
|
|
603
|
+
past the cell padding (the old cell-relative percentages overflowed
|
|
604
|
+
tight columns by the padding width). */
|
|
605
|
+
.bar-track {
|
|
606
|
+
position: absolute;
|
|
607
|
+
top: var(--bar-margin-y);
|
|
608
|
+
bottom: var(--bar-margin-y);
|
|
609
|
+
left: var(--table-cell-padding-x);
|
|
610
|
+
right: var(--table-cell-padding-x);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.bar-track .bar-background {
|
|
614
|
+
top: 0;
|
|
615
|
+
bottom: 0;
|
|
616
|
+
left: 0;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.bar-track .bar-value {
|
|
620
|
+
margin-left: 0;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/* Scoped to table cells: the ChartFactory bar builders also emit an SVG
|
|
624
|
+
<text class="bar-value"> for their end-of-bar labels, and an unscoped rule
|
|
625
|
+
here (position/top/transform) leaks onto those and floats them off their
|
|
626
|
+
bars. Table bar-values are always a <div> inside a <td>, so `td` scoping
|
|
627
|
+
fixes the collision without changing table appearance. */
|
|
628
|
+
td .bar-value {
|
|
629
|
+
position: absolute;
|
|
630
|
+
z-index: 1;
|
|
631
|
+
font-weight: var(--font-weight-semibold);
|
|
632
|
+
text-align: right;
|
|
633
|
+
color: var(--color-black);
|
|
634
|
+
padding: 0 var(--bar-text-padding);
|
|
635
|
+
white-space: nowrap;
|
|
636
|
+
top: 50%;
|
|
637
|
+
transform: translateY(-50%);
|
|
638
|
+
margin-left: var(--table-cell-padding-x);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/* ===========================
|
|
642
|
+
HEATMAP CELLS
|
|
643
|
+
=========================== */
|
|
644
|
+
|
|
645
|
+
.heatmap-cell-padded {
|
|
646
|
+
position: relative;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.heatmap-cell-padded.align-right {
|
|
650
|
+
text-align: right;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.heatmap-box {
|
|
654
|
+
display: inline-flex;
|
|
655
|
+
align-items: center;
|
|
656
|
+
justify-content: center;
|
|
657
|
+
padding: 0 var(--heatmap-box-padding);
|
|
658
|
+
border-radius: var(--heatmap-box-border-radius);
|
|
659
|
+
font-weight: var(--font-weight-semibold);
|
|
660
|
+
text-align: center;
|
|
661
|
+
min-width: var(--heatmap-box-min-width);
|
|
662
|
+
height: var(--heatmap-box-height);
|
|
663
|
+
box-sizing: border-box;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.heatmap-value {
|
|
667
|
+
position: relative;
|
|
668
|
+
z-index: 1;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/* Missing value in a heatmap column: quiet em dash, no fill */
|
|
672
|
+
.heatmap-box-empty {
|
|
673
|
+
color: var(--color-text-faint);
|
|
674
|
+
font-weight: var(--font-weight-normal);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/* ===========================
|
|
678
|
+
HIGHLIGHTING
|
|
679
|
+
=========================== */
|
|
680
|
+
|
|
681
|
+
.highlight-column {
|
|
682
|
+
background-color: var(--color-bg-highlight);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.highlight-column .bar-background {
|
|
686
|
+
left: var(--space-3);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.highlight-column .bar-value {
|
|
690
|
+
margin-left: var(--space-3);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.highlight-header {
|
|
694
|
+
background-color: var(--color-bg-highlight);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.highlight-row {
|
|
698
|
+
background-color: var(--color-bg-highlight);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.highlight-row:hover {
|
|
702
|
+
background-color: var(--color-bg-highlight);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/* ===========================
|
|
706
|
+
SORTING
|
|
707
|
+
=========================== */
|
|
708
|
+
|
|
709
|
+
.sortable-header {
|
|
710
|
+
cursor: pointer;
|
|
711
|
+
position: relative;
|
|
712
|
+
user-select: none;
|
|
713
|
+
transition: background-color var(--sort-transition-duration) ease;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/* Sortability is signaled ambiently: pointer cursor + hover/focus fill.
|
|
717
|
+
Idle sortable columns show no glyph. */
|
|
718
|
+
.sortable-header:hover,
|
|
719
|
+
.sortable-header:focus-within {
|
|
720
|
+
background-color: var(--sort-hover-bg);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/* The header's click/keyboard surface — a real button for aria-sort,
|
|
724
|
+
Tab focus, and Enter/Space activation. Styled to be invisible. */
|
|
725
|
+
.sort-button {
|
|
726
|
+
background: none;
|
|
727
|
+
border: 0;
|
|
728
|
+
padding: 0;
|
|
729
|
+
margin: 0;
|
|
730
|
+
font: inherit;
|
|
731
|
+
color: inherit;
|
|
732
|
+
letter-spacing: inherit;
|
|
733
|
+
text-transform: inherit;
|
|
734
|
+
text-align: inherit;
|
|
735
|
+
cursor: pointer;
|
|
736
|
+
display: block;
|
|
737
|
+
width: 100%;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.sort-button:focus-visible {
|
|
741
|
+
outline: 2px solid var(--chart-primary, #3b82f6);
|
|
742
|
+
outline-offset: -2px;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/* Glue wrappers keep the indicator on the same line as the adjacent
|
|
746
|
+
word — the chevron can never wrap onto a line by itself. */
|
|
747
|
+
.sort-glue,
|
|
748
|
+
.sort-glue-lead {
|
|
749
|
+
white-space: nowrap;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.sort-indicator {
|
|
753
|
+
display: inline-block;
|
|
754
|
+
color: var(--sort-indicator-color);
|
|
755
|
+
opacity: var(--sort-indicator-opacity, 0.5);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
.sort-indicator.inactive {
|
|
759
|
+
display: none;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.sort-glue .sort-indicator.active {
|
|
763
|
+
margin-left: var(--space-1);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.sort-glue-lead .sort-indicator.active {
|
|
767
|
+
margin-right: var(--space-1);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/* CSS-drawn chevron: the same bordered box for both directions, rotated
|
|
771
|
+
about its center — size, stroke, and baseline are exactly consistent.
|
|
772
|
+
The rotation leaves the visible strokes entirely on one side of the
|
|
773
|
+
box center (below it for ▾, above it for ▴), so each direction gets a
|
|
774
|
+
compensating translate of size × √2/4 that puts the glyph's visual
|
|
775
|
+
center exactly on the box center — up and down share one center point. */
|
|
776
|
+
.sort-indicator.asc::after,
|
|
777
|
+
.sort-indicator.desc::after {
|
|
778
|
+
content: '';
|
|
779
|
+
display: inline-block;
|
|
780
|
+
width: var(--sort-chevron-size, 5px);
|
|
781
|
+
height: var(--sort-chevron-size, 5px);
|
|
782
|
+
border-right: var(--sort-chevron-stroke, 1.5px) solid currentColor;
|
|
783
|
+
border-bottom: var(--sort-chevron-stroke, 1.5px) solid currentColor;
|
|
784
|
+
vertical-align: 1px;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.sort-indicator.desc::after {
|
|
788
|
+
transform: translateY(calc(var(--sort-chevron-size, 5px) * -0.354)) rotate(45deg);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.sort-indicator.asc::after {
|
|
792
|
+
transform: translateY(calc(var(--sort-chevron-size, 5px) * 0.354)) rotate(225deg);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/* Sorted column highlighting — quiet neutral wash over the full column,
|
|
796
|
+
header cell included, applied when the user sorts manually. */
|
|
797
|
+
.sorted-column {
|
|
798
|
+
background-color: var(--table-sorted-column-bg, rgba(0, 0, 0, 0.03));
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/* A first column is flush-left by default; when it carries a column
|
|
802
|
+
wash (sorted tint, permanent highlight) the text would hug the
|
|
803
|
+
shaded edge, so those cells get the small near-edge padding. Row-
|
|
804
|
+
level shading (hover, highlight-row) is deliberately excluded —
|
|
805
|
+
per-row padding would misalign first-column text across rows. */
|
|
806
|
+
th:first-child.sorted-column,
|
|
807
|
+
td:first-child.sorted-column,
|
|
808
|
+
th:first-child.highlight-header,
|
|
809
|
+
td:first-child.highlight-column {
|
|
810
|
+
padding-left: var(--table-cell-padding-near);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
.sorted-column:hover {
|
|
814
|
+
background-color: var(--table-sorted-column-bg-hover, rgba(0, 0, 0, 0.05));
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
th.sorted-column {
|
|
818
|
+
color: var(--color-text-primary, var(--color-black));
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/* ===========================
|
|
822
|
+
TABLE TOOLTIPS
|
|
823
|
+
=========================== */
|
|
824
|
+
|
|
825
|
+
.d3-table-tooltip {
|
|
826
|
+
position: absolute;
|
|
827
|
+
background: var(--color-bg-container);
|
|
828
|
+
border: 1px solid var(--color-border-tooltip);
|
|
829
|
+
border-radius: var(--tooltip-border-radius);
|
|
830
|
+
padding: var(--tooltip-padding);
|
|
831
|
+
box-shadow: var(--shadow-tooltip);
|
|
832
|
+
z-index: 1000;
|
|
833
|
+
pointer-events: none;
|
|
834
|
+
font-family: var(--font-family);
|
|
835
|
+
font-size: var(--font-size-md);
|
|
836
|
+
min-width: var(--tooltip-min-width);
|
|
837
|
+
overflow: hidden;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.d3-table-tooltip-chart {
|
|
841
|
+
display: flex;
|
|
842
|
+
align-items: flex-end;
|
|
843
|
+
gap: var(--tooltip-bar-gap);
|
|
844
|
+
height: var(--tooltip-chart-height);
|
|
845
|
+
position: relative;
|
|
846
|
+
box-sizing: border-box;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
.d3-table-tooltip-bar {
|
|
850
|
+
flex: 1;
|
|
851
|
+
background-color: var(--color-text-muted);
|
|
852
|
+
border-radius: var(--tooltip-bar-border-radius);
|
|
853
|
+
min-width: var(--tooltip-bar-min-width);
|
|
854
|
+
position: relative;
|
|
855
|
+
max-height: var(--tooltip-chart-height);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.d3-table-tooltip-bar-label {
|
|
859
|
+
position: absolute;
|
|
860
|
+
bottom: calc(-1 * var(--tooltip-label-offset-y));
|
|
861
|
+
left: 50%;
|
|
862
|
+
transform: translateX(-50%);
|
|
863
|
+
font-size: var(--font-size-xs);
|
|
864
|
+
color: var(--color-text-muted);
|
|
865
|
+
white-space: nowrap;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
.d3-table-tooltip-bar-value {
|
|
869
|
+
position: absolute;
|
|
870
|
+
top: calc(-1 * var(--tooltip-value-offset-y));
|
|
871
|
+
left: 50%;
|
|
872
|
+
transform: translateX(-50%);
|
|
873
|
+
font-size: var(--font-size-sm);
|
|
874
|
+
font-weight: var(--font-weight-semibold);
|
|
875
|
+
color: var(--color-black);
|
|
876
|
+
white-space: nowrap;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/* ===========================
|
|
880
|
+
RESPONSIVE
|
|
881
|
+
=========================== */
|
|
882
|
+
|
|
883
|
+
@media (max-width: 768px) {
|
|
884
|
+
.d3-table-container {
|
|
885
|
+
padding: var(--container-padding-mobile);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
.d3-table-title {
|
|
889
|
+
font-size: var(--title-font-size-mobile);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
table {
|
|
893
|
+
font-size: var(--table-font-size-mobile);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
th {
|
|
897
|
+
font-size: var(--table-header-font-size-mobile);
|
|
898
|
+
}
|
|
899
|
+
}
|