bo-grid 0.8.0 → 0.21.0
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 +202 -9
- package/dist/bo-grid.FilterMenu-BHI6rILc.js +154 -0
- package/dist/bo-grid.ToolPanel-C3u-4YKc.js +34 -0
- package/dist/bo-grid.element-DPnHUXMa.js +6623 -0
- package/dist/bo-grid.element.js +4 -0
- package/dist/charts/BarChart.svelte +50 -0
- package/dist/charts/BarChart.svelte.d.ts +16 -0
- package/dist/charts/DonutChart.svelte +54 -0
- package/dist/charts/DonutChart.svelte.d.ts +18 -0
- package/dist/charts/Legend.svelte +47 -0
- package/dist/charts/Legend.svelte.d.ts +12 -0
- package/dist/charts/LineChart.svelte +59 -0
- package/dist/charts/LineChart.svelte.d.ts +14 -0
- package/dist/charts/StackedBarChart.svelte +56 -0
- package/dist/charts/StackedBarChart.svelte.d.ts +18 -0
- package/dist/charts/chart-math.d.ts +57 -0
- package/dist/charts/chart-math.js +174 -0
- package/dist/charts/index.d.ts +8 -0
- package/dist/charts/index.js +11 -0
- package/dist/charts/palette.d.ts +4 -0
- package/dist/charts/palette.js +14 -0
- package/dist/format/format.d.ts +6 -0
- package/dist/format/format.js +41 -0
- package/dist/grid/Cell.svelte +247 -8
- package/dist/grid/Cell.svelte.d.ts +6 -0
- package/dist/grid/FilterMenu.svelte +7 -0
- package/dist/grid/Grid.svelte +307 -85
- package/dist/grid/Grid.svelte.d.ts +19 -0
- package/dist/grid/GroupRow.svelte +5 -2
- package/dist/grid/Pager.svelte +4 -0
- package/dist/grid/RowMenu.svelte +65 -2
- package/dist/grid/ToolPanel.svelte +5 -0
- package/dist/grid/column.d.ts +133 -0
- package/dist/grid/column.js +133 -4
- package/dist/grid/colvirt.d.ts +15 -0
- package/dist/grid/colvirt.js +43 -0
- package/dist/grid/export.js +5 -2
- package/dist/grid/filtering.d.ts +5 -2
- package/dist/grid/filtering.js +5 -4
- package/dist/grid/grouping.d.ts +30 -0
- package/dist/grid/grouping.js +33 -0
- package/dist/grid/theme.d.ts +15 -0
- package/dist/grid/theme.js +78 -0
- package/dist/grid/tree.d.ts +19 -7
- package/dist/grid/tree.js +16 -11
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -2
- package/package.json +12 -2
package/dist/format/format.js
CHANGED
|
@@ -23,3 +23,44 @@ export function fmtDate(ms, style = 'medium') {
|
|
|
23
23
|
: { month: 'short', day: 'numeric', year: 'numeric' };
|
|
24
24
|
return new Date(ms).toLocaleDateString('en-US', opts);
|
|
25
25
|
}
|
|
26
|
+
/** Localized currency (e.g. `$1,234.50`). Falls back to a fixed-decimal number
|
|
27
|
+
if the ISO `currency` code is unsupported. */
|
|
28
|
+
export function fmtCurrency(v, currency = 'USD', locale = 'en-US', decimals) {
|
|
29
|
+
if (!Number.isFinite(v))
|
|
30
|
+
return '';
|
|
31
|
+
const fd = decimals != null ? { minimumFractionDigits: decimals, maximumFractionDigits: decimals } : {};
|
|
32
|
+
try {
|
|
33
|
+
return new Intl.NumberFormat(locale, { style: 'currency', currency, ...fd }).format(v);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return v.toFixed(decimals ?? 2);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Coarse relative-time thresholds (seconds → unit divisor + label).
|
|
40
|
+
const RT = [
|
|
41
|
+
[3600, 60, 'min'],
|
|
42
|
+
[86_400, 3600, 'hour'],
|
|
43
|
+
[604_800, 86_400, 'day'],
|
|
44
|
+
[2_629_800, 604_800, 'week'],
|
|
45
|
+
[31_557_600, 2_629_800, 'month'],
|
|
46
|
+
[Infinity, 31_557_600, 'year'],
|
|
47
|
+
];
|
|
48
|
+
/** Human relative time vs `now` (default: real now) — e.g. `3 hours ago`,
|
|
49
|
+
`in 2 days`. `ms` is an epoch timestamp. Deterministic when `now` is passed. */
|
|
50
|
+
export function relativeTime(ms, now = Date.now()) {
|
|
51
|
+
if (!Number.isFinite(ms))
|
|
52
|
+
return '';
|
|
53
|
+
const secs = Math.round((now - ms) / 1000); // >0 = past
|
|
54
|
+
const past = secs >= 0;
|
|
55
|
+
const a = Math.abs(secs);
|
|
56
|
+
if (a < 45)
|
|
57
|
+
return past ? 'just now' : 'soon';
|
|
58
|
+
for (const [ceil, div, unit] of RT) {
|
|
59
|
+
if (a < ceil) {
|
|
60
|
+
const n = Math.floor(a / div);
|
|
61
|
+
const label = `${n} ${unit}${n === 1 ? '' : 's'}`;
|
|
62
|
+
return past ? `${label} ago` : `in ${label}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return '';
|
|
66
|
+
}
|
package/dist/grid/Cell.svelte
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { ColumnDef, GridRow } from './column';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
formatCell,
|
|
6
|
+
colStyle,
|
|
7
|
+
candlesOf,
|
|
8
|
+
isNumeric,
|
|
9
|
+
cellValue,
|
|
10
|
+
dataBarGeometry,
|
|
11
|
+
colorScaleBackground,
|
|
12
|
+
pickIcon,
|
|
13
|
+
toneColor,
|
|
14
|
+
safeHref,
|
|
15
|
+
} from './column';
|
|
5
16
|
import { heatColor } from './heatmap';
|
|
6
17
|
import Sparkline from '../sparkline/Sparkline.svelte';
|
|
7
18
|
|
|
@@ -21,6 +32,7 @@
|
|
|
21
32
|
seed = null,
|
|
22
33
|
fillCorner = false,
|
|
23
34
|
fillpreview = false,
|
|
35
|
+
cfRange = null,
|
|
24
36
|
colIndex,
|
|
25
37
|
cellId,
|
|
26
38
|
cellSnippet,
|
|
@@ -54,6 +66,9 @@
|
|
|
54
66
|
fillCorner?: boolean;
|
|
55
67
|
/** This cell is inside the in-progress fill drag's preview range. */
|
|
56
68
|
fillpreview?: boolean;
|
|
69
|
+
/** Conditional-formatting data extent (min/max over the view) for this
|
|
70
|
+
column; null when the column has no `dataBar`/`colorScale`. */
|
|
71
|
+
cfRange?: { min: number; max: number } | null;
|
|
57
72
|
colIndex?: number;
|
|
58
73
|
cellId?: string;
|
|
59
74
|
cellSnippet?: Snippet<[{ row: GridRow; column: ColumnDef; value: unknown }]>;
|
|
@@ -70,6 +85,16 @@
|
|
|
70
85
|
onFillStart?: () => void;
|
|
71
86
|
} = $props();
|
|
72
87
|
|
|
88
|
+
// Avatar initials: first letters of the first two words.
|
|
89
|
+
function initials(name: string): string {
|
|
90
|
+
return name
|
|
91
|
+
.split(/\s+/)
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
.slice(0, 2)
|
|
94
|
+
.map((w) => w[0]?.toUpperCase() ?? '')
|
|
95
|
+
.join('');
|
|
96
|
+
}
|
|
97
|
+
|
|
73
98
|
let cancelled = false;
|
|
74
99
|
function focusSelect(node: HTMLInputElement) {
|
|
75
100
|
node.focus();
|
|
@@ -103,8 +128,9 @@
|
|
|
103
128
|
|
|
104
129
|
// Dynamic field read. row is a runes class instance, so row[col.key] still
|
|
105
130
|
// goes through the $state getter — fine-grained reactivity is preserved even
|
|
106
|
-
// though the key is only known at runtime.
|
|
107
|
-
|
|
131
|
+
// though the key is only known at runtime. Computed columns derive from the
|
|
132
|
+
// whole row via cellValue (their value() reads the row's $state getters too).
|
|
133
|
+
const value = $derived(cellValue(col, row));
|
|
108
134
|
// Typed inline editor: date columns edit with a date picker, numeric columns
|
|
109
135
|
// with a numeric input; everything else stays a text input.
|
|
110
136
|
const editorType = $derived(col.type === 'date' ? 'date' : isNumeric(col) ? 'number' : 'text');
|
|
@@ -113,7 +139,9 @@
|
|
|
113
139
|
? new Date(Number(value)).toISOString().slice(0, 10)
|
|
114
140
|
: String(value ?? ''),
|
|
115
141
|
);
|
|
116
|
-
|
|
142
|
+
// Alignment kind: numbers right-align (tabular); sparkline + text-like rich
|
|
143
|
+
// types (tags/badge/boolean/avatar) left-align.
|
|
144
|
+
const kind = $derived(col.type === 'sparkline' ? 'spark' : isNumeric(col) ? 'num' : 'text');
|
|
117
145
|
// Optional per-column cell class (static string or value/row function).
|
|
118
146
|
const extraClass = $derived(
|
|
119
147
|
typeof col.cellClass === 'function' ? (col.cellClass(value, row) ?? '') : (col.cellClass ?? ''),
|
|
@@ -125,14 +153,36 @@
|
|
|
125
153
|
: undefined,
|
|
126
154
|
);
|
|
127
155
|
|
|
156
|
+
// ---- Conditional formatting (v0.10): data bar + icon set ----
|
|
157
|
+
// Geometry/threshold logic lives in column.ts (pure, unit-tested); here we map
|
|
158
|
+
// it to CSS (left/width %, tone → colour).
|
|
159
|
+
const hasCf = $derived(!!col.dataBar || !!col.icons);
|
|
160
|
+
const bar = $derived.by(() => {
|
|
161
|
+
if (!col.dataBar || !cfRange) return null;
|
|
162
|
+
const g = dataBarGeometry(value, cfRange, col.dataBar);
|
|
163
|
+
if (!g) return null;
|
|
164
|
+
const color = g.negative ? (col.dataBar.negative ?? 'var(--bo-down)') : (col.dataBar.color ?? 'var(--bo-up)');
|
|
165
|
+
return { left: `${g.left * 100}%`, width: `${g.width * 100}%`, color };
|
|
166
|
+
});
|
|
167
|
+
const icon = $derived.by(() => {
|
|
168
|
+
const pick = col.icons ? pickIcon(value, col.icons) : null;
|
|
169
|
+
return pick ? { icon: pick.icon, color: toneColor(pick.tone) } : null;
|
|
170
|
+
});
|
|
171
|
+
// Colour-scale cell tint (applied as a cell background in cellStyle).
|
|
172
|
+
const scaleBg = $derived(col.colorScale && cfRange ? colorScaleBackground(value, cfRange, col.colorScale) : null);
|
|
173
|
+
|
|
128
174
|
function cellStyle(): string {
|
|
129
175
|
let s = width != null ? `flex:0 0 ${width}px;width:${width}px;` : colStyle(col);
|
|
130
|
-
|
|
176
|
+
// Conditional background: heatmap type, else a colour-scale tint (both translucent).
|
|
177
|
+
const bg = col.type === 'heatmap' ? heatColor(Number(value), col.min, col.max) : scaleBg;
|
|
131
178
|
if (pinned) {
|
|
132
179
|
s += `position:sticky;${pinSide}:${pinOffset}px;z-index:1;`;
|
|
133
|
-
// Pinned cells must be opaque to cover scrolled content
|
|
134
|
-
//
|
|
135
|
-
|
|
180
|
+
// Pinned cells must be opaque to cover scrolled content — layer any
|
|
181
|
+
// translucent tint over the (alternating) row colour.
|
|
182
|
+
const rowBg = `var(${alt ? '--bo-row-a' : '--bo-row-b'})`;
|
|
183
|
+
s += bg ? `background:linear-gradient(${bg},${bg}),${rowBg};` : `background:${rowBg};`;
|
|
184
|
+
} else if (bg) {
|
|
185
|
+
s += `background:${bg};`;
|
|
136
186
|
}
|
|
137
187
|
return s;
|
|
138
188
|
}
|
|
@@ -223,8 +273,55 @@
|
|
|
223
273
|
{#if cellSnippet}{@render cellSnippet({ row, column: col, value })}{:else}{value ?? ''}{/if}
|
|
224
274
|
{:else if col.type === 'sparkline'}
|
|
225
275
|
<Sparkline candles={candlesOf(row, col.sparkKey)} />
|
|
276
|
+
{:else if col.type === 'progress'}
|
|
277
|
+
{@const lo = col.min ?? 0}
|
|
278
|
+
{@const pct = Math.max(0, Math.min(100, (((Number(value) || 0) - lo) / (((col.max ?? 100) - lo) || 1)) * 100))}
|
|
279
|
+
<span class="bo-progress" title={String(value ?? '')}>
|
|
280
|
+
<span class="bo-progress-fill" style="width:{pct}%"></span>
|
|
281
|
+
</span>
|
|
282
|
+
{:else if col.type === 'rating'}
|
|
283
|
+
{@const rmax = col.max ?? 5}
|
|
284
|
+
{@const r = Math.max(0, Math.min(rmax, Math.round(Number(value) || 0)))}
|
|
285
|
+
<span class="bo-rating" aria-label="{r} out of {rmax}">
|
|
286
|
+
<span class="bo-stars-on">{'★'.repeat(r)}</span><span class="bo-stars-off">{'★'.repeat(rmax - r)}</span>
|
|
287
|
+
</span>
|
|
288
|
+
{:else if col.type === 'tags'}
|
|
289
|
+
{@const tags = Array.isArray(value) ? value : String(value ?? '').split(',').map((s) => s.trim()).filter(Boolean)}
|
|
290
|
+
<span class="bo-tags">{#each tags as t (t)}<span class="bo-tag">{t}</span>{/each}</span>
|
|
291
|
+
{:else if col.type === 'badge'}
|
|
292
|
+
<span class="bo-badge bo-badge-{col.tones?.[String(value)] ?? 'neutral'}">{value ?? ''}</span>
|
|
293
|
+
{:else if col.type === 'boolean'}
|
|
294
|
+
{#if value}
|
|
295
|
+
<span class="bo-bool bo-bool-yes">✓{#if col.trueLabel} {col.trueLabel}{/if}</span>
|
|
296
|
+
{:else}
|
|
297
|
+
<span class="bo-bool bo-bool-no">✕{#if col.falseLabel} {col.falseLabel}{/if}</span>
|
|
298
|
+
{/if}
|
|
299
|
+
{:else if col.type === 'avatar'}
|
|
300
|
+
<span class="bo-avatar" aria-hidden="true">{initials(String(value ?? ''))}</span>
|
|
301
|
+
<span class="bo-avatar-name">{value ?? ''}{#if col.sub}<em>{row[col.sub]}</em>{/if}</span>
|
|
302
|
+
{:else if col.type === 'link'}
|
|
303
|
+
{@const href = safeHref(col.href ? col.href(row) : String(value ?? ''))}
|
|
304
|
+
{#if href}<a
|
|
305
|
+
class="bo-link"
|
|
306
|
+
{href}
|
|
307
|
+
target={col.newTab ? '_blank' : undefined}
|
|
308
|
+
rel={col.newTab ? 'noopener noreferrer' : undefined}
|
|
309
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
310
|
+
onclick={(e) => e.stopPropagation()}>{value ?? ''}</a>{:else}{value ?? ''}{/if}
|
|
226
311
|
{:else if col.type === 'text'}
|
|
227
312
|
<strong>{formatCell(col, value, row)}</strong>{#if col.sub}<em>{row[col.sub]}</em>{/if}
|
|
313
|
+
{:else if hasCf}
|
|
314
|
+
{#if bar}<span class="bo-databar" style="left:{bar.left};width:{bar.width};background:{bar.color}"></span>{/if}
|
|
315
|
+
{#key col.flash ? row.flashSeq : 0}
|
|
316
|
+
<span
|
|
317
|
+
class="bo-cf-val"
|
|
318
|
+
class:flash={col.flash}
|
|
319
|
+
class:up={col.flash && row.flashDir === 'up'}
|
|
320
|
+
class:down={col.flash && row.flashDir === 'down'}
|
|
321
|
+
>
|
|
322
|
+
{#if icon}<span class="bo-cf-icon" style="color:{icon.color}">{icon.icon}</span>{/if}{formatCell(col, value, row)}
|
|
323
|
+
</span>
|
|
324
|
+
{/key}
|
|
228
325
|
{:else if col.flash}
|
|
229
326
|
{#key row.flashSeq}
|
|
230
327
|
<span class="flash {row.flashDir}">{formatCell(col, value, row)}</span>
|
|
@@ -267,6 +364,141 @@
|
|
|
267
364
|
.text {
|
|
268
365
|
gap: 6px;
|
|
269
366
|
}
|
|
367
|
+
|
|
368
|
+
/* ---- Rich cell types (v0.9) — all colours from theme tokens ---- */
|
|
369
|
+
.bo-progress {
|
|
370
|
+
flex: 1;
|
|
371
|
+
min-width: 36px;
|
|
372
|
+
height: 6px;
|
|
373
|
+
border-radius: 999px;
|
|
374
|
+
background: var(--bo-row-hover);
|
|
375
|
+
overflow: hidden;
|
|
376
|
+
}
|
|
377
|
+
.bo-progress-fill {
|
|
378
|
+
display: block;
|
|
379
|
+
height: 100%;
|
|
380
|
+
background: var(--bo-up);
|
|
381
|
+
border-radius: 999px;
|
|
382
|
+
}
|
|
383
|
+
.bo-rating {
|
|
384
|
+
letter-spacing: 1px;
|
|
385
|
+
white-space: nowrap;
|
|
386
|
+
}
|
|
387
|
+
.bo-stars-on {
|
|
388
|
+
color: var(--bo-amber);
|
|
389
|
+
}
|
|
390
|
+
.bo-stars-off {
|
|
391
|
+
color: var(--bo-border);
|
|
392
|
+
}
|
|
393
|
+
.bo-tags {
|
|
394
|
+
display: flex;
|
|
395
|
+
gap: 4px;
|
|
396
|
+
overflow: hidden;
|
|
397
|
+
}
|
|
398
|
+
.bo-tag {
|
|
399
|
+
padding: 1px 7px;
|
|
400
|
+
font-size: 11px;
|
|
401
|
+
color: var(--bo-text-dim);
|
|
402
|
+
background: var(--bo-row-hover);
|
|
403
|
+
border: 0.5px solid var(--bo-border);
|
|
404
|
+
border-radius: 999px;
|
|
405
|
+
white-space: nowrap;
|
|
406
|
+
}
|
|
407
|
+
.bo-badge {
|
|
408
|
+
padding: 2px 9px;
|
|
409
|
+
font-size: 11px;
|
|
410
|
+
font-weight: 600;
|
|
411
|
+
border-radius: 999px;
|
|
412
|
+
white-space: nowrap;
|
|
413
|
+
}
|
|
414
|
+
.bo-badge-up {
|
|
415
|
+
color: var(--bo-up);
|
|
416
|
+
background: color-mix(in srgb, var(--bo-up) 15%, transparent);
|
|
417
|
+
}
|
|
418
|
+
.bo-badge-down {
|
|
419
|
+
color: var(--bo-down);
|
|
420
|
+
background: color-mix(in srgb, var(--bo-down) 15%, transparent);
|
|
421
|
+
}
|
|
422
|
+
.bo-badge-amber {
|
|
423
|
+
color: var(--bo-amber);
|
|
424
|
+
background: color-mix(in srgb, var(--bo-amber) 15%, transparent);
|
|
425
|
+
}
|
|
426
|
+
.bo-badge-info {
|
|
427
|
+
color: var(--bo-sel-border);
|
|
428
|
+
background: color-mix(in srgb, var(--bo-sel-border) 15%, transparent);
|
|
429
|
+
}
|
|
430
|
+
.bo-badge-neutral {
|
|
431
|
+
color: var(--bo-text-dim);
|
|
432
|
+
background: var(--bo-row-hover);
|
|
433
|
+
}
|
|
434
|
+
.bo-link {
|
|
435
|
+
color: var(--bo-sel-border);
|
|
436
|
+
text-decoration: none;
|
|
437
|
+
overflow: hidden;
|
|
438
|
+
text-overflow: ellipsis;
|
|
439
|
+
}
|
|
440
|
+
.bo-link:hover {
|
|
441
|
+
text-decoration: underline;
|
|
442
|
+
}
|
|
443
|
+
.bo-bool-yes {
|
|
444
|
+
color: var(--bo-up);
|
|
445
|
+
font-weight: 600;
|
|
446
|
+
}
|
|
447
|
+
.bo-bool-no {
|
|
448
|
+
color: var(--bo-text-dim);
|
|
449
|
+
}
|
|
450
|
+
.bo-avatar {
|
|
451
|
+
display: inline-flex;
|
|
452
|
+
align-items: center;
|
|
453
|
+
justify-content: center;
|
|
454
|
+
flex: 0 0 auto;
|
|
455
|
+
width: 22px;
|
|
456
|
+
height: 22px;
|
|
457
|
+
font-size: 9px;
|
|
458
|
+
font-weight: 700;
|
|
459
|
+
color: var(--bo-bg);
|
|
460
|
+
background: var(--bo-text-dim);
|
|
461
|
+
border-radius: 50%;
|
|
462
|
+
}
|
|
463
|
+
.bo-avatar-name {
|
|
464
|
+
min-width: 0;
|
|
465
|
+
overflow: hidden;
|
|
466
|
+
text-overflow: ellipsis;
|
|
467
|
+
}
|
|
468
|
+
.bo-avatar-name em {
|
|
469
|
+
margin-left: 6px;
|
|
470
|
+
font-style: normal;
|
|
471
|
+
font-size: 11px;
|
|
472
|
+
color: var(--bo-text-dim);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/* ---- Conditional formatting (v0.10): data bars + icon sets ---- */
|
|
476
|
+
.bo-databar {
|
|
477
|
+
position: absolute;
|
|
478
|
+
top: 50%;
|
|
479
|
+
height: 62%;
|
|
480
|
+
transform: translateY(-50%);
|
|
481
|
+
border-radius: 2px;
|
|
482
|
+
opacity: 0.22;
|
|
483
|
+
z-index: 0;
|
|
484
|
+
pointer-events: none;
|
|
485
|
+
}
|
|
486
|
+
/* Value sits above its data bar; carries the (optional) flash colour. */
|
|
487
|
+
.bo-cf-val {
|
|
488
|
+
position: relative;
|
|
489
|
+
z-index: 1;
|
|
490
|
+
display: inline-flex;
|
|
491
|
+
align-items: center;
|
|
492
|
+
gap: 5px;
|
|
493
|
+
min-width: 0;
|
|
494
|
+
overflow: hidden;
|
|
495
|
+
text-overflow: ellipsis;
|
|
496
|
+
}
|
|
497
|
+
.bo-cf-icon {
|
|
498
|
+
flex: none;
|
|
499
|
+
font-size: 11px;
|
|
500
|
+
line-height: 1;
|
|
501
|
+
}
|
|
270
502
|
.text strong {
|
|
271
503
|
font-family: var(--bo-mono);
|
|
272
504
|
font-weight: 600;
|
|
@@ -301,6 +533,13 @@
|
|
|
301
533
|
color: var(--bo-text);
|
|
302
534
|
background: var(--bo-row-hover);
|
|
303
535
|
}
|
|
536
|
+
/* Visible keyboard focus (WCAG 2.4.7). */
|
|
537
|
+
.tree-toggle:focus-visible,
|
|
538
|
+
.bo-link:focus-visible {
|
|
539
|
+
outline: 2px solid var(--bo-sel-border);
|
|
540
|
+
outline-offset: -1px;
|
|
541
|
+
border-radius: 3px;
|
|
542
|
+
}
|
|
304
543
|
.tree-leaf {
|
|
305
544
|
display: inline-block;
|
|
306
545
|
width: 18px;
|
|
@@ -21,6 +21,12 @@ type $$ComponentProps = {
|
|
|
21
21
|
fillCorner?: boolean;
|
|
22
22
|
/** This cell is inside the in-progress fill drag's preview range. */
|
|
23
23
|
fillpreview?: boolean;
|
|
24
|
+
/** Conditional-formatting data extent (min/max over the view) for this
|
|
25
|
+
column; null when the column has no `dataBar`/`colorScale`. */
|
|
26
|
+
cfRange?: {
|
|
27
|
+
min: number;
|
|
28
|
+
max: number;
|
|
29
|
+
} | null;
|
|
24
30
|
colIndex?: number;
|
|
25
31
|
cellId?: string;
|
|
26
32
|
cellSnippet?: Snippet<[{
|
|
@@ -255,6 +255,13 @@
|
|
|
255
255
|
.bo-fm-btn:hover {
|
|
256
256
|
color: var(--bo-text);
|
|
257
257
|
}
|
|
258
|
+
/* Visible keyboard focus (WCAG 2.4.7) for the menu's custom buttons. */
|
|
259
|
+
.bo-fm-btn:focus-visible,
|
|
260
|
+
.bo-fm-link:focus-visible {
|
|
261
|
+
outline: 2px solid var(--bo-sel-border);
|
|
262
|
+
outline-offset: 1px;
|
|
263
|
+
border-radius: 5px;
|
|
264
|
+
}
|
|
258
265
|
.bo-fm-apply {
|
|
259
266
|
color: #0a0a0a;
|
|
260
267
|
background: var(--bo-up);
|