Chart builder: SelectControl pickers, channel chooser, per-type aggregates

This commit is contained in:
2026-06-12 14:45:31 +03:00
parent 4dcff4601d
commit ed66fe9c05
15 changed files with 1232 additions and 276 deletions
+102 -51
View File
@@ -103,18 +103,6 @@
gap: var(--space-2);
}
.filterField {
flex: 1;
min-width: 0;
padding: var(--space-2) var(--space-3);
border: var(--border-width) solid var(--border-strong);
border-radius: var(--radius);
background: var(--bg);
color: var(--text);
font: inherit;
font-size: 13px;
}
.filterPredicate {
display: flex;
align-items: center;
@@ -173,7 +161,6 @@
font-family: var(--font-mono);
}
.filterField:focus-visible,
.calcName:focus-visible,
.valueInput:focus-visible,
.exprInput:focus-visible {
@@ -428,16 +415,6 @@
color: var(--text-placeholder);
}
.mini {
padding: var(--space-1) var(--space-2);
border: var(--border-width) solid var(--border-strong);
border-radius: var(--radius);
background: var(--bg);
color: var(--text);
font: inherit;
font-size: 12px;
}
.toggle {
display: inline-flex;
align-items: center;
@@ -451,31 +428,11 @@
gap: var(--space-5);
}
.mini:focus-visible,
.dimInput:focus-visible {
outline: 2px solid var(--focus);
outline-offset: -1px;
}
.dimensions {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.dimInputs {
display: flex;
gap: var(--space-3);
}
.dimField {
display: flex;
flex-direction: column;
gap: var(--space-1);
font-size: 12px;
color: var(--text-secondary);
}
.dimInput {
width: 100px;
padding: var(--space-2) var(--space-3);
@@ -713,6 +670,44 @@
color: var(--text-secondary);
}
/* Scroll viewport around the field lists: a wide dataset (Superstore: 25 columns)
must not push the Marks card and chart controls below the fold. Group headings
pin to the top while their list scrolls under them. */
.shelfScroll {
display: flex;
flex-direction: column;
gap: var(--space-2);
max-height: 280px;
overflow-y: auto;
}
/* An armed channel makes the shelf the live target — say so where the next click
happens (NN/g #1): an accent ring on the list plus the status line above it. */
.shelfArmed {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--radius);
}
.armedHint {
margin: 0;
font-size: 12px;
color: var(--text-secondary);
}
.armedHint strong {
color: var(--accent);
}
.shelfScroll .shelfGroupHead {
position: sticky;
top: 0;
z-index: 1;
margin-top: 0;
padding: var(--space-2) 0;
background: var(--layer-01);
}
.shelfList {
display: flex;
flex-direction: column;
@@ -785,11 +780,14 @@
gap: var(--space-2);
}
/* Pill — a bound channel (field/count or constant). */
/* Pill — a bound channel (field/count or constant). The per-channel transform
controls (Aggregate/Bin/Granularity) flow inline beside the pill, wrapping only
when the row truly runs out of room (a narrow window; the Marks card). */
.pillWrap {
display: flex;
flex-direction: column;
gap: var(--space-2);
flex-wrap: wrap;
align-items: center;
gap: var(--space-2) var(--space-3);
min-width: 0;
}
@@ -827,6 +825,10 @@
opacity: 0.85;
}
.pillType[aria-expanded='true'] {
background: color-mix(in srgb, #000 30%, var(--accent));
}
.pillType:focus-visible {
outline: 2px solid var(--accent-contrast);
outline-offset: -2px;
@@ -841,6 +843,8 @@
}
.pillName {
/* Grow into any pill slack so the remove ✕ stays at the pill's right edge. */
flex: 1;
min-width: 0;
padding: var(--space-2) var(--space-3);
overflow: hidden;
@@ -937,19 +941,22 @@
outline-offset: 1px;
}
/* Ghost button (Carbon: links navigate, buttons act — this one acts). */
.slotConst {
padding: var(--space-1);
padding: var(--space-1) var(--space-2);
border: none;
border-radius: var(--radius);
background: none;
color: var(--accent);
font: inherit;
font-size: 11px;
white-space: nowrap;
text-decoration: underline;
cursor: pointer;
transition: background var(--dur-fast) var(--ease);
}
.slotConst:hover {
background: var(--layer-02);
color: var(--accent-hover);
}
@@ -966,10 +973,12 @@
padding: var(--space-5) var(--space-5) 0;
}
/* Swap sits beside the heading — an action flushed to the far edge of a very wide
pane reads as unrelated to the shelves it acts on. */
.shelvesHead {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-4);
}
.shelfStrip {
@@ -1000,11 +1009,53 @@
padding: var(--space-2) var(--space-3);
}
.shelfSlots .slot,
.shelfSlots .pillWrap {
/* Empty slots keep a minimum presence; a filled pill hugs its content so the
remove ✕ sits at its right edge rather than adrift in dead pill background. */
.shelfSlots .slot {
min-width: 190px;
}
/* Chart properties — title/subtitle/size as a slim strip pinned under the preview
(chart-level properties live on the chart side; council 2026-06-12). */
.chartProps {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-4);
padding: var(--space-3) var(--space-5);
border-top: var(--border-width) solid var(--border);
}
.propField {
display: inline-flex;
align-items: center;
gap: var(--space-2);
font-size: 12px;
color: var(--text-secondary);
white-space: nowrap;
}
.propInput {
width: 200px;
padding: var(--space-1) var(--space-2);
border: var(--border-width) solid var(--border-strong);
border-radius: var(--radius);
background: var(--bg);
color: var(--text);
font: inherit;
font-size: 12px;
}
.propInput:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.propInput:focus-visible {
outline: 2px solid var(--focus);
outline-offset: -1px;
}
/* Reserved faceting placeholder (Phase 4) — non-interactive, signals where it'll live. */
.facetSlot {
display: flex;