tablewalk 0.0.1
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 +553 -0
- package/dist/adapters/adapter.js +372 -0
- package/dist/adapters/connect.js +33 -0
- package/dist/adapters/mysql.js +951 -0
- package/dist/adapters/postgres.js +1000 -0
- package/dist/adapters/sqlite.js +781 -0
- package/dist/client/agent.js +262 -0
- package/dist/client/app.js +973 -0
- package/dist/client/arrange.js +254 -0
- package/dist/client/ask.js +133 -0
- package/dist/client/breakdown.js +317 -0
- package/dist/client/clauses.js +390 -0
- package/dist/client/columns.js +98 -0
- package/dist/client/complete.js +437 -0
- package/dist/client/compose.js +166 -0
- package/dist/client/composer.css +495 -0
- package/dist/client/composer.js +1972 -0
- package/dist/client/connections.js +234 -0
- package/dist/client/connmanager.js +962 -0
- package/dist/client/connurl.js +188 -0
- package/dist/client/core.js +893 -0
- package/dist/client/deeplink.js +270 -0
- package/dist/client/delete.js +144 -0
- package/dist/client/diagram.js +885 -0
- package/dist/client/dropdown.js +279 -0
- package/dist/client/export.js +456 -0
- package/dist/client/features.css +524 -0
- package/dist/client/findvalue.js +169 -0
- package/dist/client/grid.js +205 -0
- package/dist/client/handoff.js +153 -0
- package/dist/client/help.css +145 -0
- package/dist/client/help.js +881 -0
- package/dist/client/history.js +222 -0
- package/dist/client/index.html +116 -0
- package/dist/client/insert.js +151 -0
- package/dist/client/menu.js +160 -0
- package/dist/client/nested.js +255 -0
- package/dist/client/page.css +713 -0
- package/dist/client/page.js +1345 -0
- package/dist/client/pagebuilder.js +1222 -0
- package/dist/client/pagemarks.js +95 -0
- package/dist/client/palette.js +374 -0
- package/dist/client/peek.js +254 -0
- package/dist/client/picker.js +139 -0
- package/dist/client/pins.js +140 -0
- package/dist/client/prompt.js +129 -0
- package/dist/client/record.js +707 -0
- package/dist/client/schemaexport.js +242 -0
- package/dist/client/schematext.js +125 -0
- package/dist/client/shape.js +178 -0
- package/dist/client/shapecheck.js +129 -0
- package/dist/client/skeleton.js +139 -0
- package/dist/client/sql.css +126 -0
- package/dist/client/sql.js +398 -0
- package/dist/client/sqlcomplete.js +163 -0
- package/dist/client/sqlsaved.js +107 -0
- package/dist/client/style.css +2711 -0
- package/dist/client/summary.js +259 -0
- package/dist/client/table.js +1035 -0
- package/dist/client/template.js +539 -0
- package/dist/client/theme.js +74 -0
- package/dist/client/tour.js +324 -0
- package/dist/client/undo.js +105 -0
- package/dist/client/url.js +166 -0
- package/dist/client/value.js +223 -0
- package/dist/client/views.js +215 -0
- package/dist/client/virtual.js +176 -0
- package/dist/client/welcome.js +170 -0
- package/dist/client/write.js +414 -0
- package/dist/server/changeimpact.js +195 -0
- package/dist/server/connections.js +615 -0
- package/dist/server/constraints.js +62 -0
- package/dist/server/credentials.js +230 -0
- package/dist/server/fixture.js +199 -0
- package/dist/server/graph.js +194 -0
- package/dist/server/impact.js +48 -0
- package/dist/server/index.js +2204 -0
- package/dist/server/journal.js +173 -0
- package/dist/server/layouts.js +128 -0
- package/dist/server/mcp.js +2840 -0
- package/dist/server/shapeonly.js +91 -0
- package/dist/shared/breakdown.js +231 -0
- package/dist/shared/breakdowntext.js +257 -0
- package/dist/shared/diff.js +130 -0
- package/dist/shared/like.js +29 -0
- package/dist/shared/lint.js +149 -0
- package/dist/shared/order.js +133 -0
- package/dist/shared/page.js +932 -0
- package/dist/shared/query.js +831 -0
- package/dist/shared/recordview.js +343 -0
- package/dist/shared/schema.js +377 -0
- package/dist/shared/sqlsaved.js +67 -0
- package/dist/shared/view.js +981 -0
- package/dist/shared/viewtext.js +273 -0
- package/dist/shared/vocabulary.js +164 -0
- package/package.json +57 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
/* Pages: one root, and everything that hangs off it.
|
|
2
|
+
|
|
3
|
+
In its own file because it is a self-contained surface and style.css is
|
|
4
|
+
already long. Nothing here names a colour — every value is a token defined
|
|
5
|
+
at the top of style.css, so the dark scheme needs no second set of rules. */
|
|
6
|
+
|
|
7
|
+
/* Two rows — see `.page-head-top` below. The head was a single flex row and
|
|
8
|
+
an earlier rule here set it, so the two-row version quietly did nothing. */
|
|
9
|
+
.page-head { margin-bottom: 14px; }
|
|
10
|
+
.page-title { margin: 0; font: 600 16px var(--mono); letter-spacing: -0.015em; }
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/* ---------- the layout ---------- */
|
|
14
|
+
|
|
15
|
+
/* Centred, and the head sits on the same measure as the body so the title
|
|
16
|
+
lines up with the first field under it rather than with the window. */
|
|
17
|
+
.page-head, .page-body { max-width: 76rem; margin-inline: auto; }
|
|
18
|
+
/* Two rows: what the page is called, then how you choose the record it is
|
|
19
|
+
about. They were one row, and the filter — the widest thing there and the
|
|
20
|
+
only one you type into — ended up wedged between two buttons. */
|
|
21
|
+
.page-head { margin-bottom: 18px; }
|
|
22
|
+
.page-head-top {
|
|
23
|
+
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
|
24
|
+
margin-bottom: 12px;
|
|
25
|
+
}
|
|
26
|
+
/* The Layout button goes to the far end, away from the name it is not about. */
|
|
27
|
+
.page-head-top .page-edit { margin-left: auto; }
|
|
28
|
+
/* The title, when it is also the way to another page. Sized like the heading
|
|
29
|
+
it replaces rather than like a form control, because it is still the name
|
|
30
|
+
of what you are looking at — the caret is the only thing saying there is
|
|
31
|
+
more than one of them. */
|
|
32
|
+
.page-title-pick .dd-button {
|
|
33
|
+
padding: 2px 8px 2px 4px;
|
|
34
|
+
border-color: transparent; background: none;
|
|
35
|
+
}
|
|
36
|
+
.page-title-pick .dd-value {
|
|
37
|
+
font: 620 17px var(--mono); letter-spacing: -0.01em; color: var(--text);
|
|
38
|
+
}
|
|
39
|
+
.page-title-pick .dd-button:hover { border-color: var(--border); background: var(--surface-2); }
|
|
40
|
+
.page-title-pick .dd-list { min-width: 16rem; }
|
|
41
|
+
/* The Edit control to the far right; everything else keeps to the left, so
|
|
42
|
+
the filter sits beside the title it belongs to rather than adrift. */
|
|
43
|
+
.page-edit { flex: none; margin-left: auto; font-size: 11.5px; }
|
|
44
|
+
|
|
45
|
+
/* Sections flow into rows, each taking the share of the row its width names.
|
|
46
|
+
Wrapping rather than a grid with declared tracks: a section is a piece of
|
|
47
|
+
content and "half" survives a narrow window, where "columns 4 through 9"
|
|
48
|
+
needs a breakpoint table to mean anything. */
|
|
49
|
+
.page-body {
|
|
50
|
+
/* Sections need room to read as separate things. A page is several
|
|
51
|
+
answers about one record, and at 26px the seam between two of them was
|
|
52
|
+
the same distance as the gap between two rows of fields inside one — so
|
|
53
|
+
the eye had to use the hairline to find the edge, and a section without
|
|
54
|
+
a heading had nothing to find. */
|
|
55
|
+
--page-row-gap: 40px;
|
|
56
|
+
display: flex; flex-wrap: wrap;
|
|
57
|
+
column-gap: 32px; row-gap: var(--page-row-gap);
|
|
58
|
+
align-items: flex-start;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* A section's own table brings its bottom padding, which lands on top of the
|
|
62
|
+
row gap and makes the space under a list visibly bigger than the space
|
|
63
|
+
under a metric strip. The gap is the layout's job here. */
|
|
64
|
+
.page-section .table-wrap { padding-bottom: 0; }
|
|
65
|
+
|
|
66
|
+
/* No box.
|
|
67
|
+
|
|
68
|
+
Every section in a rounded card with its own border made six boxes on a
|
|
69
|
+
page, and a border around content that is already a table draws the same
|
|
70
|
+
line twice. Separation comes from space and one hairline above the
|
|
71
|
+
heading — which is what the eye needs to see a group, and all it needs.
|
|
72
|
+
The cards also fixed a width to the content inside them, so a five-row
|
|
73
|
+
table and a two-row one drew boxes of visibly different weight for no
|
|
74
|
+
reason a reader could name. */
|
|
75
|
+
.page-section {
|
|
76
|
+
flex: 1 1 100%;
|
|
77
|
+
min-width: 0;
|
|
78
|
+
padding: 0;
|
|
79
|
+
background: none;
|
|
80
|
+
border: none;
|
|
81
|
+
border-top: 1px solid var(--border);
|
|
82
|
+
padding-top: 10px;
|
|
83
|
+
}
|
|
84
|
+
/* `calc` rather than a bare percentage: the gap comes out of the row, so two
|
|
85
|
+
halves at 50% each wrap onto separate lines the moment there is one. */
|
|
86
|
+
.page-section.width-half { flex-basis: calc(50% - 16px); }
|
|
87
|
+
.page-section.width-third { flex-basis: calc(33.333% - 22px); }
|
|
88
|
+
|
|
89
|
+
/* Offsets: empty space standing where a section of that fraction would have
|
|
90
|
+
stood. The pixel corrections make the margin end exactly where the next
|
|
91
|
+
column would begin — a third-offset third lines up under the middle of
|
|
92
|
+
three, not a gap-width to its left — except `quarter`, which has no column
|
|
93
|
+
to align to and is trimmed a little so a quarter–half–quarter row cannot
|
|
94
|
+
round itself past 100% and wrap. */
|
|
95
|
+
.page-section.offset-l-quarter { margin-left: calc(25% + 6px); }
|
|
96
|
+
.page-section.offset-l-third { margin-left: calc(33.333% + 10px); }
|
|
97
|
+
.page-section.offset-l-half { margin-left: calc(50% + 16px); }
|
|
98
|
+
.page-section.offset-l-two-thirds { margin-left: calc(66.666% + 20px); }
|
|
99
|
+
.page-section.offset-r-quarter { margin-right: calc(25% + 6px); }
|
|
100
|
+
.page-section.offset-r-third { margin-right: calc(33.333% + 10px); }
|
|
101
|
+
.page-section.offset-r-half { margin-right: calc(50% + 16px); }
|
|
102
|
+
.page-section.offset-r-two-thirds { margin-right: calc(66.666% + 20px); }
|
|
103
|
+
|
|
104
|
+
@media (max-width: 900px) {
|
|
105
|
+
.page-section.width-half, .page-section.width-third { flex-basis: 100%; }
|
|
106
|
+
/* A narrow screen stacks everything full-width; an offset there is not
|
|
107
|
+
placement, it is a squeezed column. */
|
|
108
|
+
.page-section[class*="offset-"] { margin-left: 0; margin-right: 0; }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* The first section is the record itself and wants no rule above it: there is
|
|
112
|
+
nothing above it to be separated from. */
|
|
113
|
+
.page-section:first-child { border-top: none; padding-top: 0; }
|
|
114
|
+
|
|
115
|
+
/* A section heading carries a tab's weight and none of its furniture.
|
|
116
|
+
|
|
117
|
+
Drawing it as a chip did make it legible — it had been a faint grey line
|
|
118
|
+
beside content that was not — but a page then had a box round every
|
|
119
|
+
heading on it, and boxes are what the sections deliberately do not have.
|
|
120
|
+
The weight and the colour are what were missing; the border was not. */
|
|
121
|
+
.page-section-head {
|
|
122
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
123
|
+
margin: 0 0 10px;
|
|
124
|
+
font: 600 10px var(--mono); text-transform: uppercase; letter-spacing: 0.07em;
|
|
125
|
+
color: var(--text);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* A section that could not run says so in its own place, and the page keeps
|
|
129
|
+
its shape around it. A page is most useful on the day something is
|
|
130
|
+
misconfigured, which is the day an error instead of a page is worst. */
|
|
131
|
+
.page-broken { border-top-color: var(--danger); }
|
|
132
|
+
.page-broken .page-section-head { color: var(--danger); }
|
|
133
|
+
|
|
134
|
+
/* ---------- fields ---------- */
|
|
135
|
+
|
|
136
|
+
/* Label above value, not beside it.
|
|
137
|
+
|
|
138
|
+
Beside it, a fixed label column put two hundred pixels between `code` and
|
|
139
|
+
`NZ` — and with three pairs to a row, the eye had to work out which value
|
|
140
|
+
belonged to which label on every line. That is what makes a record read as
|
|
141
|
+
jumbled: not the amount of data, the distance between a thing and its name.
|
|
142
|
+
|
|
143
|
+
Stacked, each pair is one object. The label is small and quiet because it
|
|
144
|
+
is the same on every record; the value is what you came to read. */
|
|
145
|
+
/* Rows further apart than columns, and both further apart than they were.
|
|
146
|
+
Twenty-seven fields at a 14px rhythm is a wall of text where every line is
|
|
147
|
+
the same distance from the next; the label and its value belong together
|
|
148
|
+
and the next pair does not, and the spacing is the only thing that can say
|
|
149
|
+
so. */
|
|
150
|
+
.page-field-grid {
|
|
151
|
+
display: grid;
|
|
152
|
+
gap: 20px 28px;
|
|
153
|
+
grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
|
|
154
|
+
}
|
|
155
|
+
/* A folded group of fields.
|
|
156
|
+
|
|
157
|
+
`<details>` rather than a button and a class, because the browser already
|
|
158
|
+
knows how to open and close one, and the state lives on the element instead
|
|
159
|
+
of in a variable something else has to keep in step. The marker is replaced
|
|
160
|
+
with a chevron that turns, so the heading reads the same folded or not. */
|
|
161
|
+
.page-fold > summary {
|
|
162
|
+
cursor: pointer;
|
|
163
|
+
/* inline-flex, so the chip is the width of its name rather than the width
|
|
164
|
+
of the page — a full-width bar reads as a rule, not a label. */
|
|
165
|
+
display: inline-flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
gap: 6px;
|
|
168
|
+
list-style: none;
|
|
169
|
+
user-select: none;
|
|
170
|
+
}
|
|
171
|
+
/* A shut group is still legible and no longer the thing in front. */
|
|
172
|
+
.page-fold:not([open]) > summary { color: var(--text-dim); }
|
|
173
|
+
.page-fold > summary::-webkit-details-marker { display: none; }
|
|
174
|
+
.page-fold > summary::before {
|
|
175
|
+
content: '\203a';
|
|
176
|
+
display: inline-block;
|
|
177
|
+
font-size: 13px;
|
|
178
|
+
line-height: 1;
|
|
179
|
+
color: var(--text-faint);
|
|
180
|
+
transition: transform 120ms ease;
|
|
181
|
+
}
|
|
182
|
+
.page-fold[open] > summary::before { transform: rotate(90deg); }
|
|
183
|
+
.page-fold > summary:focus-visible {
|
|
184
|
+
outline: 2px solid var(--accent);
|
|
185
|
+
outline-offset: 2px;
|
|
186
|
+
border-radius: 3px;
|
|
187
|
+
}
|
|
188
|
+
.page-fold > .page-field-grid { margin-top: 10px; }
|
|
189
|
+
|
|
190
|
+
.page-field { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
191
|
+
/* An editable field is the same field with a box round the value, so a page
|
|
192
|
+
in write mode reads as the page it already was rather than as a form. */
|
|
193
|
+
.page-field-edit .cell-edit {
|
|
194
|
+
width: 100%; min-width: 0;
|
|
195
|
+
padding: 2px 6px;
|
|
196
|
+
font: 13px var(--mono); color: var(--text);
|
|
197
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
|
|
198
|
+
}
|
|
199
|
+
.page-field-edit .cell-edit:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
|
|
200
|
+
.page-field-edit .cell-edit.dirty { border-color: var(--warn); background: var(--warn-soft, var(--surface-2)); }
|
|
201
|
+
.page-pending { flex: 1 0 100%; }
|
|
202
|
+
/* A label has to be readable before it can be quiet.
|
|
203
|
+
|
|
204
|
+
Uppercase at ten pixels in the faintest colour on the page was a caption
|
|
205
|
+
of a caption: the value under it was legible and the name of the value was
|
|
206
|
+
not, which is the wrong way round on a screen whose whole job is saying
|
|
207
|
+
what the numbers are. One step darker and half a pixel larger buys that
|
|
208
|
+
back; weight does not — thirty bold labels on a screen compete with the
|
|
209
|
+
thirty values they are labelling, which is the same problem the other way
|
|
210
|
+
round. The record view's labels have always been dimmed text at regular
|
|
211
|
+
weight and they read fine. */
|
|
212
|
+
.page-field dt {
|
|
213
|
+
font: 10.5px var(--mono); text-transform: uppercase; letter-spacing: 0.05em;
|
|
214
|
+
color: var(--text-dim);
|
|
215
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
216
|
+
}
|
|
217
|
+
.page-field dd {
|
|
218
|
+
margin: 0; font: 13px var(--mono); color: var(--text);
|
|
219
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
220
|
+
}
|
|
221
|
+
.page-field dd.null { color: var(--text-faint); font-style: italic; }
|
|
222
|
+
|
|
223
|
+
/* ---------- metrics ---------- */
|
|
224
|
+
|
|
225
|
+
/* A metric is as wide as its number, and metrics sit in one strip.
|
|
226
|
+
|
|
227
|
+
Widths are declared per section, and for a list that is right — but a
|
|
228
|
+
two-digit count stretched to a third of the page is four inches of nothing,
|
|
229
|
+
and two of them read as a layout that failed rather than as a summary. They
|
|
230
|
+
take the space they need and the row fills left to right.
|
|
231
|
+
|
|
232
|
+
`full` stays the exception, for someone who wants a number on its own. */
|
|
233
|
+
.page-section.page-metric {
|
|
234
|
+
flex: 0 1 auto; min-width: 7rem;
|
|
235
|
+
border-top: none; padding-top: 0;
|
|
236
|
+
padding-right: 32px;
|
|
237
|
+
}
|
|
238
|
+
.page-section.page-metric.width-full { flex: 1 1 100%; }
|
|
239
|
+
/* A hairline between counts rather than a box around each: the run reads as
|
|
240
|
+
one strip of related numbers, which is what it is. */
|
|
241
|
+
.page-metric + .page-metric { border-left: 1px solid var(--border); padding-left: 32px; }
|
|
242
|
+
.page-metric .page-section-head { margin-bottom: 3px; }
|
|
243
|
+
|
|
244
|
+
.page-metric-value {
|
|
245
|
+
display: flex; align-items: baseline; gap: 8px;
|
|
246
|
+
width: 100%; padding: 0; text-align: left;
|
|
247
|
+
background: none; border: none; cursor: pointer;
|
|
248
|
+
color: var(--text);
|
|
249
|
+
}
|
|
250
|
+
.metric-number {
|
|
251
|
+
font: 600 26px var(--mono); letter-spacing: -0.02em;
|
|
252
|
+
font-variant-numeric: tabular-nums;
|
|
253
|
+
}
|
|
254
|
+
.metric-of { font: 11px var(--mono); color: var(--text-faint); }
|
|
255
|
+
.page-metric-value:hover .metric-number { color: var(--accent); }
|
|
256
|
+
.page-metric-value:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
|
|
257
|
+
|
|
258
|
+
/* ---------- the root bar ---------- */
|
|
259
|
+
|
|
260
|
+
/* One row, always there. The filter is the page's own control, not a door it
|
|
261
|
+
passed through — changing which record the page is about should not mean
|
|
262
|
+
going back to an empty screen. */
|
|
263
|
+
/* Its own row now, so it takes the width it needs rather than the width
|
|
264
|
+
left over after a title and two buttons. */
|
|
265
|
+
.root-wrap { position: relative; min-width: 0; }
|
|
266
|
+
.root-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
267
|
+
|
|
268
|
+
/* One control, not four.
|
|
269
|
+
|
|
270
|
+
Four separately bordered pills read as four unrelated things to fill in,
|
|
271
|
+
and on a page whose whole language is hairlines and whitespace they were
|
|
272
|
+
the only rounded objects left. Segmented: one quiet ground, one small
|
|
273
|
+
radius, and a rule between each field — which is what they are, one filter
|
|
274
|
+
with several columns. */
|
|
275
|
+
.root-fields[hidden] { display: none; }
|
|
276
|
+
.root-fields {
|
|
277
|
+
display: flex; align-items: stretch;
|
|
278
|
+
flex: 1 1 auto; min-width: 0;
|
|
279
|
+
background: var(--surface-2);
|
|
280
|
+
border: 1px solid var(--border);
|
|
281
|
+
border-radius: 6px;
|
|
282
|
+
overflow: hidden;
|
|
283
|
+
}
|
|
284
|
+
/* Big enough to type a name into. At 11.5px in a 4px-tall box these read as
|
|
285
|
+
decoration on a header rather than as the way to choose the record the
|
|
286
|
+
whole page is about — which is what they are, and on an empty page the
|
|
287
|
+
only thing to do. */
|
|
288
|
+
.root-field {
|
|
289
|
+
flex: 1 1 9rem; min-width: 5rem; max-width: 15rem;
|
|
290
|
+
padding: 7px 12px;
|
|
291
|
+
font: 12.5px var(--mono);
|
|
292
|
+
background: none; color: var(--text);
|
|
293
|
+
border: none; border-radius: 0;
|
|
294
|
+
}
|
|
295
|
+
/* The divider belongs between fields, so the first one has none — and it is
|
|
296
|
+
the field's own border rather than a separate element, which keeps the
|
|
297
|
+
group one box however many columns a table turns out to have. */
|
|
298
|
+
.root-field + .root-field { border-left: 1px solid var(--border); }
|
|
299
|
+
/* Focus and "has something in it" were both a lighter ground, so a filled
|
|
300
|
+
field looked exactly like the focused one and there was no way to see where
|
|
301
|
+
the caret was. Focus keeps the ground and adds a ring; the ring is inset so
|
|
302
|
+
it does not break the segmented group's outer edge. */
|
|
303
|
+
.root-field:focus {
|
|
304
|
+
outline: none;
|
|
305
|
+
background: var(--surface);
|
|
306
|
+
box-shadow: inset 0 0 0 1px var(--accent);
|
|
307
|
+
}
|
|
308
|
+
.root-field::placeholder { color: var(--text-faint); }
|
|
309
|
+
/* A field with something in it stays lit after focus moves on, so a filter
|
|
310
|
+
that is doing something does not look like one that is not. */
|
|
311
|
+
.root-field:not(:placeholder-shown) { background: var(--surface); color: var(--text); }
|
|
312
|
+
|
|
313
|
+
/* The record in hand, as the filter's answer rather than as a separate
|
|
314
|
+
heading. The one thing on the row that is a state and not a control, so it
|
|
315
|
+
is the one thing that carries colour. */
|
|
316
|
+
.root-current {
|
|
317
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
318
|
+
flex: none;
|
|
319
|
+
padding: 3px 8px 3px 10px;
|
|
320
|
+
background: var(--accent-soft); border: 1px solid transparent;
|
|
321
|
+
color: var(--accent); font: 12px var(--mono); cursor: pointer;
|
|
322
|
+
max-width: 20rem;
|
|
323
|
+
border-radius: 5px;
|
|
324
|
+
}
|
|
325
|
+
.root-current:hover { border-color: var(--accent); }
|
|
326
|
+
.root-current:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
327
|
+
.root-current .chip-x { opacity: 0.65; font-size: 11px; }
|
|
328
|
+
.root-current > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
329
|
+
|
|
330
|
+
.root-more { position: relative; flex: none; }
|
|
331
|
+
.root-more-head {
|
|
332
|
+
padding: 3px 8px; border-radius: 4px; cursor: pointer; list-style: none;
|
|
333
|
+
color: var(--text-faint); font: 13px var(--mono); line-height: 1;
|
|
334
|
+
}
|
|
335
|
+
.root-more-head::-webkit-details-marker { display: none; }
|
|
336
|
+
.root-more-head:hover { background: var(--surface-2); color: var(--text); }
|
|
337
|
+
.root-menu {
|
|
338
|
+
position: absolute; right: 0; top: calc(100% + 4px); z-index: 50;
|
|
339
|
+
min-width: 17rem; max-height: 24rem; overflow-y: auto;
|
|
340
|
+
padding: 6px;
|
|
341
|
+
background: var(--surface);
|
|
342
|
+
border: 1px solid var(--border-strong); border-radius: 8px;
|
|
343
|
+
box-shadow: 0 10px 34px rgb(0 0 0 / 0.18);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* Results hang below the row rather than pushing the page down: a list that
|
|
347
|
+
moved the sections every time someone typed would make the page flinch. */
|
|
348
|
+
/* Suggestions under the boxes: recents, then the first rows there are.
|
|
349
|
+
Scoped to the results panel, because the `···` menu has groups of its own
|
|
350
|
+
and they are a menu's groups, drawn tighter. */
|
|
351
|
+
.root-results .root-group + .root-group { margin-top: 14px; padding-top: 0; border-top: none; }
|
|
352
|
+
.root-results .root-group-head { margin: 0 0 6px; color: var(--text-dim); }
|
|
353
|
+
|
|
354
|
+
.root-results {
|
|
355
|
+
position: absolute; left: 0; top: calc(100% + 4px); z-index: 50;
|
|
356
|
+
min-width: 22rem; max-width: 34rem;
|
|
357
|
+
}
|
|
358
|
+
/* Except when there is no record chosen. Then this is not a dropdown over
|
|
359
|
+
the page — it *is* the page, and floating it leaves an empty screen with a
|
|
360
|
+
menu open on top of it. */
|
|
361
|
+
.root-results.root-offered {
|
|
362
|
+
position: static; margin-top: 12px; max-width: 46rem;
|
|
363
|
+
}
|
|
364
|
+
.root-results.root-offered:not(:empty) { box-shadow: none; border-color: var(--border); }
|
|
365
|
+
.root-results:not(:empty) {
|
|
366
|
+
padding: 4px;
|
|
367
|
+
background: var(--surface);
|
|
368
|
+
border: 1px solid var(--border-strong); border-radius: 8px;
|
|
369
|
+
box-shadow: 0 10px 34px rgb(0 0 0 / 0.18);
|
|
370
|
+
max-height: 22rem; overflow-y: auto;
|
|
371
|
+
}
|
|
372
|
+
.root-hits { display: flex; flex-direction: column; gap: 1px; }
|
|
373
|
+
.root-hit, .root-menu-item {
|
|
374
|
+
display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
|
|
375
|
+
width: 100%; padding: 5px 9px; text-align: left;
|
|
376
|
+
background: none; border: 1px solid transparent; border-radius: var(--radius);
|
|
377
|
+
color: var(--text); font: 12.5px var(--mono); cursor: pointer;
|
|
378
|
+
}
|
|
379
|
+
.root-hit:hover, .root-menu-item:hover { background: var(--surface-2); }
|
|
380
|
+
.root-hit:focus-visible, .root-menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
381
|
+
.root-hit-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: none; }
|
|
382
|
+
.root-hit-key { font-size: 11px; color: var(--text-faint); flex: none; }
|
|
383
|
+
/* Beside the name, not at the far edge.
|
|
384
|
+
|
|
385
|
+
`space-between` on a row as wide as the page put four hundred pixels
|
|
386
|
+
between "WO-100001" and the fields that tell it apart from WO-100002 —
|
|
387
|
+
which is the one job those fields have. They read as a pair now, and the
|
|
388
|
+
row is as wide as it needs to be. */
|
|
389
|
+
.root-results .root-hit { justify-content: flex-start; gap: 10px; }
|
|
390
|
+
.root-results .root-hit .root-hit-key { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
391
|
+
|
|
392
|
+
.root-group + .root-group { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); }
|
|
393
|
+
.root-group-head {
|
|
394
|
+
margin: 0 0 4px; padding: 0 9px;
|
|
395
|
+
font: 600 10px var(--mono); text-transform: uppercase; letter-spacing: 0.06em;
|
|
396
|
+
color: var(--text-faint);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.root-filter-head {
|
|
400
|
+
display: flex; align-items: baseline; gap: 8px;
|
|
401
|
+
padding: 5px 9px; border-radius: var(--radius);
|
|
402
|
+
cursor: pointer; list-style: none;
|
|
403
|
+
font: 12.5px var(--mono); color: var(--text);
|
|
404
|
+
}
|
|
405
|
+
.root-filter-head::-webkit-details-marker { display: none; }
|
|
406
|
+
.root-filter-head::before { content: "\25b8"; font-size: 9px; color: var(--text-faint); }
|
|
407
|
+
.root-filter[open] .root-filter-head::before { content: "\25be"; }
|
|
408
|
+
.root-filter-head:hover { background: var(--surface-2); }
|
|
409
|
+
.root-filter-rows { padding-left: 14px; }
|
|
410
|
+
|
|
411
|
+
.page-empty { margin: 4px 0 0; }
|
|
412
|
+
|
|
413
|
+
/* The strip a run of metrics sits in: one item on the page, several inside
|
|
414
|
+
it, so the page gets one seam after it rather than two. */
|
|
415
|
+
.page-metric-row {
|
|
416
|
+
flex: 1 0 100%;
|
|
417
|
+
display: flex; flex-wrap: wrap; align-items: stretch;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/* Related lists as tabs.
|
|
421
|
+
|
|
422
|
+
The strip carries the hairline the sections it replaced each carried, and
|
|
423
|
+
the selected tab sits on it — the border under the strip and the border of
|
|
424
|
+
the tab are the same line, which is what makes a tab read as being in
|
|
425
|
+
front of the page rather than beside it. */
|
|
426
|
+
.page-tabs { flex: 1 0 100%; min-width: 0; }
|
|
427
|
+
.page-tabstrip {
|
|
428
|
+
display: flex; flex-wrap: wrap; gap: 2px;
|
|
429
|
+
border-top: 1px solid var(--border);
|
|
430
|
+
padding-top: 8px; margin-bottom: 2px;
|
|
431
|
+
}
|
|
432
|
+
/* Square, like everything else that holds data here: the grid, the query
|
|
433
|
+
bar and the tables under these tabs are all straight-edged, and a rounded
|
|
434
|
+
tab was the one soft corner on a screen full of hard ones. */
|
|
435
|
+
.page-tab {
|
|
436
|
+
appearance: none; background: none; cursor: pointer;
|
|
437
|
+
border: 1px solid transparent; border-bottom: none;
|
|
438
|
+
border-radius: 0;
|
|
439
|
+
padding: 5px 11px 6px;
|
|
440
|
+
font: 600 10px var(--mono); text-transform: uppercase; letter-spacing: 0.07em;
|
|
441
|
+
color: var(--text-faint);
|
|
442
|
+
}
|
|
443
|
+
.page-tab:hover { color: var(--text); background: var(--surface-2); }
|
|
444
|
+
.page-tab[aria-selected="true"] {
|
|
445
|
+
color: var(--text);
|
|
446
|
+
background: var(--surface-2);
|
|
447
|
+
border-color: var(--border);
|
|
448
|
+
}
|
|
449
|
+
.page-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
450
|
+
/* The panel's own section has already drawn the line above it; inside a tab
|
|
451
|
+
strip that line is the strip's, and drawing it twice boxes the table. */
|
|
452
|
+
.page-tabpanel > .page-section { border-top: none; padding-top: 4px; }
|
|
453
|
+
.page-tabpanel > .page-section > .page-section-head { display: none; }
|
|
454
|
+
.page-tabpanel:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
.pb-tabs-toggle {
|
|
458
|
+
display: flex; align-items: center; gap: 5px; flex: none;
|
|
459
|
+
font: 11px var(--mono); color: var(--text-dim); cursor: pointer;
|
|
460
|
+
white-space: nowrap;
|
|
461
|
+
}
|
|
462
|
+
.pb-tabs-toggle input { margin: 0; }
|
|
463
|
+
|
|
464
|
+
.pb-backdrop {
|
|
465
|
+
position: fixed; inset: 0; z-index: 85;
|
|
466
|
+
display: flex; align-items: flex-start; justify-content: center;
|
|
467
|
+
padding: 6vh 16px;
|
|
468
|
+
background: rgb(0 0 0 / 0.28);
|
|
469
|
+
}
|
|
470
|
+
.pb {
|
|
471
|
+
display: flex; flex-direction: column; gap: 12px;
|
|
472
|
+
width: min(58rem, 100%); max-height: 86vh;
|
|
473
|
+
padding: 16px 18px;
|
|
474
|
+
background: var(--surface);
|
|
475
|
+
border: 1px solid var(--border-strong); border-radius: 12px;
|
|
476
|
+
box-shadow: 0 18px 50px rgb(0 0 0 / 0.26);
|
|
477
|
+
}
|
|
478
|
+
.pb-head { display: flex; align-items: baseline; gap: 10px; }
|
|
479
|
+
.pb-title { margin: 0; font: 600 15px var(--mono); }
|
|
480
|
+
.pb-base { font: 11px var(--mono); color: var(--text-faint); }
|
|
481
|
+
.pb-close { margin-left: auto; font-size: 15px; line-height: 1; padding: 2px 8px; }
|
|
482
|
+
|
|
483
|
+
.pb-label {
|
|
484
|
+
font: 10px var(--mono); text-transform: uppercase; letter-spacing: 0.06em;
|
|
485
|
+
color: var(--text-faint); flex: none;
|
|
486
|
+
}
|
|
487
|
+
.pb-name-row { display: flex; align-items: center; gap: 10px; }
|
|
488
|
+
.pb-input {
|
|
489
|
+
flex: 1 1 auto; min-width: 0;
|
|
490
|
+
padding: 5px 10px; font: 12.5px var(--mono);
|
|
491
|
+
background: var(--surface); color: var(--text);
|
|
492
|
+
border: 1px solid var(--border); border-radius: var(--radius);
|
|
493
|
+
}
|
|
494
|
+
.pb-input:focus { outline: none; border-color: var(--accent); }
|
|
495
|
+
.pb-limit { flex: 0 0 5rem; }
|
|
496
|
+
.pb-width { flex: none; }
|
|
497
|
+
|
|
498
|
+
/* The themed listbox, not the browser's own.
|
|
499
|
+
|
|
500
|
+
A native `<select>` opens the operating system's menu — a blue macOS popup
|
|
501
|
+
with its own type and its own corners, in a dialog that has neither. It is
|
|
502
|
+
the one control a page cannot style, which is exactly why this codebase
|
|
503
|
+
already had a replacement; the builder was simply not using it. */
|
|
504
|
+
.pb-section .dd { flex: none; }
|
|
505
|
+
.pb-section .dd-button {
|
|
506
|
+
padding: 4px 8px; font: 11.5px var(--mono);
|
|
507
|
+
min-width: 5.5rem;
|
|
508
|
+
}
|
|
509
|
+
.pb-section .dd-list { min-width: 12rem; }
|
|
510
|
+
|
|
511
|
+
/* The sections, in the order they will appear. Scrolls on its own so the
|
|
512
|
+
Add and Save rows stay put — a builder whose Save button walks off the
|
|
513
|
+
bottom as you add sections is a builder you have to scroll to finish. */
|
|
514
|
+
.pb-body {
|
|
515
|
+
display: flex; flex-direction: column; gap: 6px;
|
|
516
|
+
overflow-y: auto; min-height: 4rem;
|
|
517
|
+
padding: 2px;
|
|
518
|
+
}
|
|
519
|
+
.pb-section {
|
|
520
|
+
display: flex; align-items: center; gap: 8px;
|
|
521
|
+
padding: 6px 8px;
|
|
522
|
+
background: var(--surface-2);
|
|
523
|
+
border: 1px solid var(--border); border-radius: var(--radius);
|
|
524
|
+
}
|
|
525
|
+
/* The grip is the only draggable part of the row, because everything else on
|
|
526
|
+
it is an input. Faint until the row is hovered: it is a handle, not a
|
|
527
|
+
decoration, and a column of them down the dialog reads as noise. */
|
|
528
|
+
.pb-section-grip {
|
|
529
|
+
flex: none; cursor: grab; user-select: none;
|
|
530
|
+
padding: 2px 1px; margin-right: -2px;
|
|
531
|
+
font: 12px var(--mono); letter-spacing: -1px; line-height: 1;
|
|
532
|
+
color: var(--text-faint); opacity: 0.45;
|
|
533
|
+
transform: rotate(90deg);
|
|
534
|
+
transition: opacity 90ms ease;
|
|
535
|
+
}
|
|
536
|
+
.pb-section:hover .pb-section-grip { opacity: 1; }
|
|
537
|
+
.pb-section-grip:active { cursor: grabbing; }
|
|
538
|
+
.pb-section.pb-dragging { opacity: 0.45; }
|
|
539
|
+
/* Where the row will land, drawn as the line it will sit on rather than by
|
|
540
|
+
moving everything else out of the way mid-drag. */
|
|
541
|
+
.pb-section.pb-drop-above { box-shadow: 0 -2px 0 var(--accent); }
|
|
542
|
+
.pb-section.pb-drop-below { box-shadow: 0 2px 0 var(--accent); }
|
|
543
|
+
|
|
544
|
+
.pb-kind {
|
|
545
|
+
flex: 0 0 3.4rem;
|
|
546
|
+
font: 10px var(--mono); text-transform: uppercase; letter-spacing: 0.05em;
|
|
547
|
+
color: var(--text-faint);
|
|
548
|
+
}
|
|
549
|
+
.pb-section-actions { display: flex; gap: 2px; margin-left: auto; flex: none; }
|
|
550
|
+
.pb-section-actions button { padding: 2px 7px; font-size: 12px; line-height: 1; }
|
|
551
|
+
.pb-section-actions button:disabled { opacity: 0.3; cursor: default; }
|
|
552
|
+
.pb-remove:hover { color: var(--danger); }
|
|
553
|
+
|
|
554
|
+
.pb-add { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
555
|
+
.pb-picker { position: relative; }
|
|
556
|
+
.pb-add-btn {
|
|
557
|
+
display: inline-block; list-style: none; cursor: pointer;
|
|
558
|
+
padding: 3px 10px; font: 12px var(--sans);
|
|
559
|
+
background: transparent; color: var(--text-dim);
|
|
560
|
+
border: 1px solid var(--border-strong); border-radius: 999px;
|
|
561
|
+
}
|
|
562
|
+
.pb-add-btn::-webkit-details-marker { display: none; }
|
|
563
|
+
.pb-add-btn:hover { border-color: var(--accent); color: var(--accent); }
|
|
564
|
+
.pb-menu {
|
|
565
|
+
position: absolute; left: 0; bottom: calc(100% + 4px); z-index: 90;
|
|
566
|
+
min-width: 22rem; max-height: 22rem; overflow-y: auto;
|
|
567
|
+
padding: 6px;
|
|
568
|
+
background: var(--surface);
|
|
569
|
+
border: 1px solid var(--border-strong); border-radius: 8px;
|
|
570
|
+
box-shadow: 0 10px 34px rgb(0 0 0 / 0.2);
|
|
571
|
+
}
|
|
572
|
+
.pb-group + .pb-group { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); }
|
|
573
|
+
.pb-group-head {
|
|
574
|
+
margin: 0 0 4px; padding: 0 8px;
|
|
575
|
+
font: 600 10px var(--mono); text-transform: uppercase; letter-spacing: 0.06em;
|
|
576
|
+
color: var(--text-faint);
|
|
577
|
+
}
|
|
578
|
+
.pb-menu-item {
|
|
579
|
+
display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
|
|
580
|
+
width: 100%; padding: 5px 8px; text-align: left;
|
|
581
|
+
background: none; border: 1px solid transparent; border-radius: var(--radius);
|
|
582
|
+
cursor: pointer;
|
|
583
|
+
}
|
|
584
|
+
.pb-section.pb-drop { border-color: var(--accent); background: var(--accent-soft); }
|
|
585
|
+
.pb-holds { flex: none; font: 11px var(--mono); color: var(--text-faint); white-space: nowrap; }
|
|
586
|
+
.pb-menu-item:hover { background: var(--surface-2); }
|
|
587
|
+
.pb-menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
588
|
+
.pb-menu-label { font: 12.5px var(--mono); color: var(--text); }
|
|
589
|
+
/* The route, so choosing between two entries with the same table name is a
|
|
590
|
+
choice between two things you can tell apart. */
|
|
591
|
+
.pb-menu-detail { font: 10.5px var(--mono); color: var(--text-faint); }
|
|
592
|
+
|
|
593
|
+
.pb-foot { display: flex; align-items: center; gap: 8px; }
|
|
594
|
+
.pb-spacer { flex: 1 1 auto; }
|
|
595
|
+
.pb-json { font-size: 11.5px; }
|
|
596
|
+
|
|
597
|
+
/* ---------- the field panel, inline ---------- */
|
|
598
|
+
|
|
599
|
+
/* A section and its open panel are one thing, so they move together and the
|
|
600
|
+
panel cannot end up beside a different row. */
|
|
601
|
+
.pb-group-row { display: flex; flex-direction: column; }
|
|
602
|
+
|
|
603
|
+
.pb-panel {
|
|
604
|
+
margin: 0 8px 2px;
|
|
605
|
+
padding: 8px 10px 10px;
|
|
606
|
+
background: var(--surface-2);
|
|
607
|
+
border: 1px solid var(--border); border-top: none;
|
|
608
|
+
border-radius: 0 0 var(--radius) var(--radius);
|
|
609
|
+
}
|
|
610
|
+
.pb-panel-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; }
|
|
611
|
+
.pb-panel-head button { padding: 2px 8px; font-size: 11px; }
|
|
612
|
+
.pb-field-count { font: 10.5px var(--mono); color: var(--text-faint); }
|
|
613
|
+
|
|
614
|
+
/* Several across, because forty names in one column is a scroll and the names
|
|
615
|
+
are short enough to read three or four abreast. */
|
|
616
|
+
.pb-field-grid {
|
|
617
|
+
display: grid; gap: 1px 10px;
|
|
618
|
+
grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
|
|
619
|
+
max-height: 34vh; overflow-y: auto;
|
|
620
|
+
}
|
|
621
|
+
/* The whole row toggles: a checkbox is a twelve-pixel target and the name
|
|
622
|
+
beside it is not. */
|
|
623
|
+
.pb-field-row {
|
|
624
|
+
display: flex; align-items: baseline; gap: 7px;
|
|
625
|
+
padding: 3px 6px; border-radius: var(--radius);
|
|
626
|
+
cursor: pointer; min-width: 0;
|
|
627
|
+
}
|
|
628
|
+
.pb-field-row:hover { background: var(--surface); }
|
|
629
|
+
.pb-field-row input { flex: none; margin: 0; }
|
|
630
|
+
.pb-field-name {
|
|
631
|
+
font: 12px var(--mono); color: var(--text);
|
|
632
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
633
|
+
}
|
|
634
|
+
.pb-field-type { font: 10px var(--mono); color: var(--text-faint); flex: none; }
|
|
635
|
+
|
|
636
|
+
/* The fields on a group, laid out the way the page will lay them out — same
|
|
637
|
+
column count, same order — so arranging them is done on a picture of the
|
|
638
|
+
result rather than on a list that has to be imagined into one. */
|
|
639
|
+
.pb-arrange {
|
|
640
|
+
display: grid; gap: 2px 10px;
|
|
641
|
+
max-height: 34vh; overflow-y: auto;
|
|
642
|
+
margin-bottom: 8px;
|
|
643
|
+
}
|
|
644
|
+
.pb-arrange-row {
|
|
645
|
+
display: flex; align-items: baseline; gap: 7px;
|
|
646
|
+
padding: 3px 4px 3px 2px; border-radius: var(--radius);
|
|
647
|
+
background: var(--surface); border: 1px solid var(--border);
|
|
648
|
+
cursor: grab; min-width: 0; position: relative;
|
|
649
|
+
}
|
|
650
|
+
.pb-arrange-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
651
|
+
.pb-arrange-row.dragging { opacity: 0.45; cursor: grabbing; }
|
|
652
|
+
.pb-arrange-row .pb-field-name { flex: 1; }
|
|
653
|
+
.pb-grip {
|
|
654
|
+
font: 12px var(--mono); color: var(--text-faint);
|
|
655
|
+
letter-spacing: -1px; flex: none; transform: rotate(90deg);
|
|
656
|
+
}
|
|
657
|
+
.pb-arrange-row .pb-send,
|
|
658
|
+
.pb-arrange-row .pb-remove {
|
|
659
|
+
flex: none; padding: 0 5px; font-size: 11px; line-height: 1.6;
|
|
660
|
+
opacity: 0; transition: opacity 90ms ease;
|
|
661
|
+
}
|
|
662
|
+
.pb-arrange-row:hover .pb-send,
|
|
663
|
+
.pb-arrange-row:hover .pb-remove,
|
|
664
|
+
.pb-arrange-row:focus-within .pb-send,
|
|
665
|
+
.pb-arrange-row:focus-within .pb-remove { opacity: 1; }
|
|
666
|
+
|
|
667
|
+
/* Which group to send a field to, opened in the row itself: a dialog here
|
|
668
|
+
would cover the arrangement being made. */
|
|
669
|
+
.pb-send-menu {
|
|
670
|
+
position: absolute; z-index: 3; top: 100%; right: 0;
|
|
671
|
+
display: flex; flex-direction: column; align-items: stretch;
|
|
672
|
+
background: var(--surface); border: 1px solid var(--border);
|
|
673
|
+
border-radius: var(--radius); padding: 3px; gap: 2px;
|
|
674
|
+
box-shadow: var(--shadow, 0 6px 18px rgb(0 0 0 / 0.12));
|
|
675
|
+
}
|
|
676
|
+
.pb-send-menu button { font-size: 11px; text-align: left; white-space: nowrap; }
|
|
677
|
+
|
|
678
|
+
/* Everything not on the page, as chips: short names, many of them, and each
|
|
679
|
+
one click from being added. */
|
|
680
|
+
.pb-pool { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px; }
|
|
681
|
+
.pb-add-field { font: 11px var(--mono); padding: 2px 7px; }
|
|
682
|
+
|
|
683
|
+
/* A hidden section keeps its row and stops looking live. */
|
|
684
|
+
.pb-section.pb-hidden { opacity: 0.5; }
|
|
685
|
+
.pb-section.pb-hidden .pb-input { text-decoration: line-through; }
|
|
686
|
+
.pb-hide { flex: none; font-size: 10px; line-height: 1.9; }
|
|
687
|
+
|
|
688
|
+
.pb-cols { flex: none; }
|
|
689
|
+
|
|
690
|
+
.pb-fields-btn { flex: none; font-size: 11px; padding: 3px 9px; }
|
|
691
|
+
.pb-fields-btn.open { border-color: var(--accent); color: var(--accent); }
|
|
692
|
+
/* The row an open panel belongs to keeps its bottom edge open, so the two
|
|
693
|
+
read as one block rather than as two stacked boxes. */
|
|
694
|
+
.pb-section.pb-open { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
|
|
695
|
+
|
|
696
|
+
/* Destructive, and at the far end from Save so the two are not neighbours. */
|
|
697
|
+
.pb-delete { color: var(--danger); }
|
|
698
|
+
.pb-delete:hover { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, transparent); }
|
|
699
|
+
|
|
700
|
+
/* A list's own pager. Right-aligned under the table, quiet, and present only
|
|
701
|
+
when there is somewhere to go — two disabled arrows under a list that fits
|
|
702
|
+
on one page are an explanation that nothing can be done. */
|
|
703
|
+
.page-pager {
|
|
704
|
+
display: flex; align-items: center; justify-content: flex-end; gap: 4px;
|
|
705
|
+
margin-top: 6px;
|
|
706
|
+
}
|
|
707
|
+
.page-range {
|
|
708
|
+
margin-right: 6px;
|
|
709
|
+
font: 11px var(--mono); color: var(--text-faint);
|
|
710
|
+
font-variant-numeric: tabular-nums;
|
|
711
|
+
}
|
|
712
|
+
.page-pager button { padding: 1px 8px; font-size: 12px; line-height: 1.4; }
|
|
713
|
+
.page-pager button:disabled { opacity: 0.3; cursor: default; }
|