@toclocoinc/lattice-grid 1.1.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/LICENSE +116 -0
- package/README.md +161 -0
- package/lattice-core.esm.js +38814 -0
- package/lattice-grid.css +4393 -0
- package/lattice-grid.d.ts +1232 -0
- package/lattice-grid.esm.js +71803 -0
- package/lattice-grid.esm.min.js +36610 -0
- package/lattice-grid.min.css +1 -0
- package/lattice-grid.min.js +36506 -0
- package/lattice-grid.umd.js +71701 -0
- package/package.json +28 -0
package/lattice-grid.css
ADDED
|
@@ -0,0 +1,4393 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Lattice Grid 1.1.0 — theme
|
|
3
|
+
* Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
|
|
4
|
+
* https://latticegrid.dev
|
|
5
|
+
*/
|
|
6
|
+
/*
|
|
7
|
+
* Lattice Grid — structural layer.
|
|
8
|
+
*
|
|
9
|
+
* The geometry the renderer depends on: the region tree of §7.2, absolutely
|
|
10
|
+
* positioned rows and cells so scrolling is a transform rather than a layout
|
|
11
|
+
* (§7.3), pinned-region stacking, and the overlay layers.
|
|
12
|
+
*
|
|
13
|
+
* This file is deliberately separate from `lattice.css`. That one is the
|
|
14
|
+
* *theme* — colours, spacing, density, decorations, all expressed as custom
|
|
15
|
+
* properties a consumer is invited to override. This one is *structure*, and
|
|
16
|
+
* overriding it breaks virtualisation. Keeping them apart makes that
|
|
17
|
+
* distinction visible in the source; the build concatenates both into the
|
|
18
|
+
* single stylesheet §3.2 promises.
|
|
19
|
+
*
|
|
20
|
+
* Rules that hold throughout, from §16:
|
|
21
|
+
* · Every selector is scoped under `.lattice`, so the grid cannot be broken
|
|
22
|
+
* by host page CSS and cannot break it.
|
|
23
|
+
* · No `!important`, anywhere.
|
|
24
|
+
* · No colour literals — everything reads a custom property defined by the
|
|
25
|
+
* theme, so a consumer retunes from one place.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/* ------------------------------------------------------------------ */
|
|
29
|
+
/* Host-page insulation */
|
|
30
|
+
/* ------------------------------------------------------------------ */
|
|
31
|
+
|
|
32
|
+
/* §16 namespaces every selector so the grid cannot leak out. Nothing stopped
|
|
33
|
+
the host page leaking *in*, and it does: a marketing site with the entirely
|
|
34
|
+
ordinary rule `section { padding: 5.5rem 0 }` put 88px of padding on every
|
|
35
|
+
filter row in the tool panel, because the grid builds those rows as
|
|
36
|
+
`<section>` elements and its own rules said nothing about padding there. A
|
|
37
|
+
CMS theme, a Tailwind preflight or a Bootstrap reset reaches just as far.
|
|
38
|
+
|
|
39
|
+
So the elements the grid creates are given a floor for the properties a host
|
|
40
|
+
is most likely to set on a bare tag. Specificity does the arranging and no
|
|
41
|
+
`!important` is needed: these selectors are (0,1,1) and every rule that
|
|
42
|
+
dresses a grid element is (0,2,0) or higher, so the grid's own styling always
|
|
43
|
+
wins and this only ever fills a gap the grid left. It is first in the
|
|
44
|
+
concatenation for the same reason — it should lose every tie it enters.
|
|
45
|
+
|
|
46
|
+
Deliberately box-model and decoration only. Nothing here touches `display`,
|
|
47
|
+
`position` or any dimension, because those are the renderer's and a reset
|
|
48
|
+
that reached them would break virtualisation rather than protect it. */
|
|
49
|
+
.lattice section,
|
|
50
|
+
.lattice div,
|
|
51
|
+
.lattice p,
|
|
52
|
+
.lattice span,
|
|
53
|
+
.lattice a,
|
|
54
|
+
.lattice ul,
|
|
55
|
+
.lattice li,
|
|
56
|
+
.lattice label,
|
|
57
|
+
.lattice img,
|
|
58
|
+
.lattice button,
|
|
59
|
+
.lattice input,
|
|
60
|
+
.lattice select,
|
|
61
|
+
.lattice textarea {
|
|
62
|
+
margin: 0;
|
|
63
|
+
padding: 0;
|
|
64
|
+
border: 0;
|
|
65
|
+
border-radius: 0;
|
|
66
|
+
background: none;
|
|
67
|
+
box-shadow: none;
|
|
68
|
+
text-align: inherit;
|
|
69
|
+
text-transform: none;
|
|
70
|
+
letter-spacing: normal;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.lattice ul,
|
|
74
|
+
.lattice li {
|
|
75
|
+
list-style: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Form controls do not inherit type from their surroundings, so the UA default
|
|
79
|
+
is what a host's `button { font-family: … }` displaces — and then the grid
|
|
80
|
+
renders its own controls in the page's brand font at the page's size. */
|
|
81
|
+
.lattice button,
|
|
82
|
+
.lattice input,
|
|
83
|
+
.lattice select,
|
|
84
|
+
.lattice textarea {
|
|
85
|
+
font: inherit;
|
|
86
|
+
line-height: inherit;
|
|
87
|
+
color: inherit;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* ------------------------------------------------------------------ */
|
|
91
|
+
/* Root */
|
|
92
|
+
/* ------------------------------------------------------------------ */
|
|
93
|
+
|
|
94
|
+
.lattice.lat-grid-root {
|
|
95
|
+
/* Icons that are shapes rather than characters, as overridable properties
|
|
96
|
+
(§16 "icons are inline SVG sprites, overridable by name"). */
|
|
97
|
+
--lattice-icon-filter: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1.5 2.5h13a.5.5 0 0 1 .38.82L10 9.2V13a.5.5 0 0 1-.28.45l-3 1.5A.5.5 0 0 1 6 14.5V9.2L1.12 3.32a.5.5 0 0 1 .38-.82z'/%3E%3C/svg%3E");
|
|
98
|
+
|
|
99
|
+
position: relative;
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
overflow: hidden;
|
|
103
|
+
box-sizing: border-box;
|
|
104
|
+
width: 100%;
|
|
105
|
+
height: 100%;
|
|
106
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
107
|
+
background: var(--lattice-background);
|
|
108
|
+
color: var(--lattice-foreground);
|
|
109
|
+
font-family: var(--lattice-font-family);
|
|
110
|
+
font-size: var(--lattice-font-size);
|
|
111
|
+
line-height: var(--lattice-line-height);
|
|
112
|
+
|
|
113
|
+
/* The grid is a single tab stop; internal navigation happens inside it
|
|
114
|
+
(§17). Focus styling lives on the focused cell, not on the container. */
|
|
115
|
+
outline: none;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.lattice.lat-grid-root *,
|
|
119
|
+
.lattice.lat-grid-root *::before,
|
|
120
|
+
.lattice.lat-grid-root *::after {
|
|
121
|
+
box-sizing: inherit;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* ------------------------------------------------------------------ */
|
|
125
|
+
/* Header */
|
|
126
|
+
/* ------------------------------------------------------------------ */
|
|
127
|
+
|
|
128
|
+
.lattice .lat-header {
|
|
129
|
+
position: relative;
|
|
130
|
+
display: flex;
|
|
131
|
+
flex: none;
|
|
132
|
+
overflow: hidden;
|
|
133
|
+
min-height: var(--lattice-header-height);
|
|
134
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-strong);
|
|
135
|
+
background: var(--lattice-surface);
|
|
136
|
+
color: var(--lattice-header-foreground);
|
|
137
|
+
font-size: var(--lattice-header-font-size);
|
|
138
|
+
font-weight: var(--lattice-header-font-weight);
|
|
139
|
+
user-select: none;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.lattice .lat-header-left-pinned,
|
|
143
|
+
.lattice .lat-header-right-pinned {
|
|
144
|
+
position: relative;
|
|
145
|
+
z-index: 2;
|
|
146
|
+
flex: none;
|
|
147
|
+
background: var(--lattice-surface);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.lattice .lat-header-left-pinned {
|
|
151
|
+
box-shadow: var(--lattice-pinned-shadow);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.lattice .lat-header-right-pinned {
|
|
155
|
+
box-shadow: var(--lattice-pinned-shadow-end);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* The centre region is clipped, and its inner track is translated in step
|
|
159
|
+
with horizontal scroll. No `left` mutation, so no layout on scroll. */
|
|
160
|
+
.lattice .lat-header-centre-clip {
|
|
161
|
+
position: relative;
|
|
162
|
+
flex: 1 1 auto;
|
|
163
|
+
overflow: hidden;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.lattice .lat-header-centre {
|
|
167
|
+
position: relative;
|
|
168
|
+
height: 100%;
|
|
169
|
+
will-change: transform;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.lattice .lat-header-row {
|
|
173
|
+
position: relative;
|
|
174
|
+
height: var(--lattice-header-height);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.lattice .lat-header-cell,
|
|
178
|
+
.lattice .lat-header-group-cell {
|
|
179
|
+
position: absolute;
|
|
180
|
+
top: 0;
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
gap: var(--lattice-gap);
|
|
184
|
+
overflow: hidden;
|
|
185
|
+
height: 100%;
|
|
186
|
+
padding: 0 var(--lattice-cell-padding-x);
|
|
187
|
+
border-right: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
188
|
+
white-space: nowrap;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.lattice .lat-header-group-cell {
|
|
192
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
193
|
+
justify-content: center;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.lattice .lat-header-label {
|
|
197
|
+
overflow: hidden;
|
|
198
|
+
flex: 1 1 auto;
|
|
199
|
+
text-overflow: ellipsis;
|
|
200
|
+
white-space: nowrap;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Alignment follows the column, so a right-aligned number column does not get
|
|
204
|
+
a left-aligned heading (§8.6). */
|
|
205
|
+
.lattice .lat-header-cell--center { justify-content: center; }
|
|
206
|
+
.lattice .lat-header-cell--end { justify-content: flex-end; }
|
|
207
|
+
.lattice .lat-header-cell--end .lat-header-label { text-align: right; }
|
|
208
|
+
|
|
209
|
+
.lattice .lat-header-sort,
|
|
210
|
+
.lattice .lat-header-filter,
|
|
211
|
+
.lattice .lat-header-menu,
|
|
212
|
+
.lattice .lat-header-group-toggle {
|
|
213
|
+
display: inline-flex;
|
|
214
|
+
flex: none;
|
|
215
|
+
align-items: center;
|
|
216
|
+
justify-content: center;
|
|
217
|
+
width: var(--lattice-icon-size);
|
|
218
|
+
height: var(--lattice-icon-size);
|
|
219
|
+
padding: 0;
|
|
220
|
+
border: 0;
|
|
221
|
+
border-radius: var(--lattice-radius-sm);
|
|
222
|
+
background: transparent;
|
|
223
|
+
color: inherit;
|
|
224
|
+
cursor: pointer;
|
|
225
|
+
opacity: 0.75;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.lattice .lat-header-sort:hover,
|
|
229
|
+
.lattice .lat-header-filter:hover,
|
|
230
|
+
.lattice .lat-header-menu:hover,
|
|
231
|
+
.lattice .lat-header-group-toggle:hover {
|
|
232
|
+
background: var(--lattice-hovered-background);
|
|
233
|
+
opacity: 1;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* The renderer emits these as empty elements and records state in `data-sort`,
|
|
237
|
+
so the glyphs belong here: an icon is a theme decision, and drawing them in
|
|
238
|
+
CSS keeps the header's per-column DOM to the elements themselves. */
|
|
239
|
+
.lattice .lat-header-sort::before { content: '\2195'; } /* up-down arrow */
|
|
240
|
+
.lattice .lat-header-cell[data-sort='asc'] .lat-header-sort::before { content: '\2191'; }
|
|
241
|
+
.lattice .lat-header-cell[data-sort='desc'] .lat-header-sort::before { content: '\2193'; }
|
|
242
|
+
/* A funnel, not an arrow. `\25BC` reads as "sort descending" sitting beside the
|
|
243
|
+
sort indicator, which is exactly the wrong thing for a filter control to say.
|
|
244
|
+
Drawn as a mask so it takes `currentColor` and stays themeable. */
|
|
245
|
+
.lattice .lat-header-filter::before {
|
|
246
|
+
content: '';
|
|
247
|
+
width: 11px;
|
|
248
|
+
height: 11px;
|
|
249
|
+
background-color: currentColor;
|
|
250
|
+
-webkit-mask: var(--lattice-icon-filter) center / contain no-repeat;
|
|
251
|
+
mask: var(--lattice-icon-filter) center / contain no-repeat;
|
|
252
|
+
}
|
|
253
|
+
.lattice .lat-header-menu::before { content: '\22EE'; } /* vertical ellipsis */
|
|
254
|
+
.lattice .lat-header-group-toggle::before { content: '\2212'; } /* minus: open */
|
|
255
|
+
.lattice .lat-header-group-cell[data-open='false'] .lat-header-group-toggle::before {
|
|
256
|
+
content: '\002B'; /* plus: closed */
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* An unsorted column shows its affordance only on hover, so a wide header does
|
|
260
|
+
not read as a row of identical icons. A sorted one always shows it. */
|
|
261
|
+
.lattice .lat-header-cell[data-sort='none'] .lat-header-sort,
|
|
262
|
+
.lattice .lat-header-filter,
|
|
263
|
+
.lattice .lat-header-menu {
|
|
264
|
+
opacity: 0;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.lattice .lat-header-cell:hover .lat-header-sort,
|
|
268
|
+
.lattice .lat-header-cell:hover .lat-header-filter,
|
|
269
|
+
.lattice .lat-header-cell:hover .lat-header-menu,
|
|
270
|
+
.lattice .lat-header-cell:focus-within .lat-header-sort,
|
|
271
|
+
.lattice .lat-header-cell:focus-within .lat-header-filter,
|
|
272
|
+
.lattice .lat-header-cell:focus-within .lat-header-menu {
|
|
273
|
+
opacity: 0.75;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* A column carrying an active filter always advertises it — hiding that until
|
|
277
|
+
hover would let a user forget why rows are missing. */
|
|
278
|
+
.lattice .lat-header-cell[data-filtered='true'] .lat-header-filter {
|
|
279
|
+
opacity: 1;
|
|
280
|
+
color: var(--lattice-accent);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* The multi-sort position index (§9.1). */
|
|
284
|
+
.lattice .lat-header-sort-index {
|
|
285
|
+
flex: none;
|
|
286
|
+
font-size: var(--lattice-font-size-sm);
|
|
287
|
+
font-variant-numeric: tabular-nums;
|
|
288
|
+
opacity: 0.7;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* Sits fully inside the cell, because the cell clips its overflow — a grip
|
|
292
|
+
hanging over the edge loses half its hit area to the clip. The renderer sets
|
|
293
|
+
the exact width inline; this is the fallback. */
|
|
294
|
+
.lattice .lat-header-resize {
|
|
295
|
+
position: absolute;
|
|
296
|
+
top: 0;
|
|
297
|
+
right: 0;
|
|
298
|
+
z-index: 2;
|
|
299
|
+
width: 10px;
|
|
300
|
+
height: 100%;
|
|
301
|
+
cursor: col-resize;
|
|
302
|
+
touch-action: none;
|
|
303
|
+
/* The gesture is a pointer drag, so the browser must not start a text
|
|
304
|
+
selection or a scroll from it. */
|
|
305
|
+
user-select: none;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.lattice .lat-header-resize:hover::after,
|
|
309
|
+
.lattice .lat-header-resize:active::after {
|
|
310
|
+
content: '';
|
|
311
|
+
position: absolute;
|
|
312
|
+
top: 15%;
|
|
313
|
+
right: 3px;
|
|
314
|
+
width: 2px;
|
|
315
|
+
height: 70%;
|
|
316
|
+
background: var(--lattice-accent);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* ------------------------------------------------------------------ */
|
|
320
|
+
/* Body */
|
|
321
|
+
/* ------------------------------------------------------------------ */
|
|
322
|
+
|
|
323
|
+
.lattice .lat-body-viewport {
|
|
324
|
+
position: relative;
|
|
325
|
+
flex: 1 1 auto;
|
|
326
|
+
overflow: auto;
|
|
327
|
+
overscroll-behavior: contain;
|
|
328
|
+
will-change: scroll-position;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* The spacer. Its height drives the scrollbar; above the browser's element
|
|
332
|
+
height cap it is clamped and offsets map through a scale factor (§7.5). */
|
|
333
|
+
/* A flex row of three regions, so the pinned ones can be `position: sticky`.
|
|
334
|
+
Sticky is the whole point: the browser holds them still on the compositor
|
|
335
|
+
thread, with no JavaScript in the scroll path. Counter-translating them from
|
|
336
|
+
a scroll handler works, but the correction lands a frame after the scroll the
|
|
337
|
+
compositor has already painted, which reads as jitter. */
|
|
338
|
+
.lattice .lat-body-canvas {
|
|
339
|
+
position: relative;
|
|
340
|
+
display: flex;
|
|
341
|
+
align-items: stretch;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.lattice .lat-body-left-pinned,
|
|
345
|
+
.lattice .lat-body-centre,
|
|
346
|
+
.lattice .lat-body-right-pinned {
|
|
347
|
+
position: relative;
|
|
348
|
+
flex: none;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* Visual order, independent of DOM order: the pinned regions are appended
|
|
352
|
+
after the centre so that they paint over it. */
|
|
353
|
+
.lattice .lat-body-left-pinned { order: 0; }
|
|
354
|
+
.lattice .lat-body-centre { order: 1; flex: 1 0 auto; }
|
|
355
|
+
.lattice .lat-body-right-pinned { order: 2; }
|
|
356
|
+
|
|
357
|
+
/* A pinned region must be opaque, or the centre scrolling beneath it shows
|
|
358
|
+
through. Its rows are absolutely positioned, so `align-items: stretch` on the
|
|
359
|
+
canvas is what gives the container height to paint — left to itself it would
|
|
360
|
+
collapse to nothing and the background would cover nothing at all.
|
|
361
|
+
`overflow: hidden` stops a cell wider than its region spilling into the
|
|
362
|
+
centre. */
|
|
363
|
+
.lattice .lat-body-left-pinned,
|
|
364
|
+
.lattice .lat-body-right-pinned {
|
|
365
|
+
position: sticky;
|
|
366
|
+
z-index: 2;
|
|
367
|
+
overflow: hidden;
|
|
368
|
+
background: var(--lattice-background);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.lattice .lat-body-left-pinned { left: 0; }
|
|
372
|
+
.lattice .lat-body-right-pinned { right: 0; }
|
|
373
|
+
|
|
374
|
+
.lattice .lat-body-left-pinned {
|
|
375
|
+
box-shadow: var(--lattice-pinned-shadow);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.lattice .lat-body-right-pinned {
|
|
379
|
+
box-shadow: var(--lattice-pinned-shadow-end);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.lattice .lat-body-centre {
|
|
383
|
+
z-index: 1;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/* ------------------------------------------------------------------ */
|
|
387
|
+
/* Rows and cells */
|
|
388
|
+
/* ------------------------------------------------------------------ */
|
|
389
|
+
|
|
390
|
+
/* Absolutely positioned and moved with translate3d, never `top` (§7.3). */
|
|
391
|
+
.lattice .lat-row {
|
|
392
|
+
position: absolute;
|
|
393
|
+
top: 0;
|
|
394
|
+
left: 0;
|
|
395
|
+
display: block;
|
|
396
|
+
width: 100%;
|
|
397
|
+
height: var(--lattice-row-height);
|
|
398
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
399
|
+
contain: layout style;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.lattice .lat-row:hover {
|
|
403
|
+
background: var(--lattice-hovered-background);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.lattice .lat-cell {
|
|
407
|
+
position: absolute;
|
|
408
|
+
top: 0;
|
|
409
|
+
display: flex;
|
|
410
|
+
align-items: center;
|
|
411
|
+
overflow: hidden;
|
|
412
|
+
height: 100%;
|
|
413
|
+
padding: var(--lattice-cell-padding-y) var(--lattice-cell-padding-x);
|
|
414
|
+
white-space: nowrap;
|
|
415
|
+
text-overflow: ellipsis;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/* A spanned cell lives in its own layer so row recycling cannot clip it
|
|
419
|
+
(§8.10). */
|
|
420
|
+
.lattice .lat-span-layer {
|
|
421
|
+
position: absolute;
|
|
422
|
+
top: 0;
|
|
423
|
+
left: 0;
|
|
424
|
+
z-index: 3;
|
|
425
|
+
pointer-events: none;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.lattice .lat-cell-span {
|
|
429
|
+
pointer-events: auto;
|
|
430
|
+
background: var(--lattice-background);
|
|
431
|
+
border-right: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
432
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/* ------------------------------------------------------------------ */
|
|
436
|
+
/* Sticky regions, overlays, status bar */
|
|
437
|
+
/* ------------------------------------------------------------------ */
|
|
438
|
+
|
|
439
|
+
.lattice .lat-sticky-top,
|
|
440
|
+
.lattice .lat-sticky-bottom {
|
|
441
|
+
position: absolute;
|
|
442
|
+
/* An absolutely positioned box is placed against the containing block's
|
|
443
|
+
padding *edge*, so it ignores the gutter the tool panel dock reserves with
|
|
444
|
+
`padding-left`/`padding-right` on the root. The header, body and status bar
|
|
445
|
+
are all in flow and shrink correctly; these rows did not, and a left-hand
|
|
446
|
+
dock cut the first column of the totals row in half. The dock publishes its
|
|
447
|
+
gutters and they are subtracted here. */
|
|
448
|
+
left: var(--lattice-dock-left, 0px);
|
|
449
|
+
right: var(--lattice-dock-right, 0px);
|
|
450
|
+
z-index: 4;
|
|
451
|
+
width: auto;
|
|
452
|
+
background: var(--lattice-background);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.lattice .lat-sticky-top {
|
|
456
|
+
top: var(--lattice-header-height);
|
|
457
|
+
box-shadow: 0 1px 0 0 var(--lattice-border-color);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/*
|
|
461
|
+
* Above the status bar, not on top of it.
|
|
462
|
+
*
|
|
463
|
+
* Both are anchored to the bottom of the root, but the status bar is in flow
|
|
464
|
+
* and the pinned rows are absolute, so `bottom: 0` put the grand total row
|
|
465
|
+
* directly over the bar — the counts were in the DOM and readable to script,
|
|
466
|
+
* and invisible on screen. The bar publishes its own height, so the offset
|
|
467
|
+
* follows it rather than being guessed at.
|
|
468
|
+
*/
|
|
469
|
+
.lattice .lat-sticky-bottom {
|
|
470
|
+
bottom: var(--lattice-status-height, 0px);
|
|
471
|
+
box-shadow: 0 -1px 0 0 var(--lattice-border-color);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* The overlay container never intercepts pointer events; individual overlays
|
|
475
|
+
opt back in, so a loading overlay blocks interaction and a drag ghost does
|
|
476
|
+
not. */
|
|
477
|
+
.lattice .lat-overlays {
|
|
478
|
+
position: absolute;
|
|
479
|
+
inset: 0;
|
|
480
|
+
z-index: 10;
|
|
481
|
+
pointer-events: none;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.lattice .lat-overlay {
|
|
485
|
+
position: absolute;
|
|
486
|
+
inset: 0;
|
|
487
|
+
display: flex;
|
|
488
|
+
align-items: center;
|
|
489
|
+
justify-content: center;
|
|
490
|
+
background: var(--lattice-overlay-background);
|
|
491
|
+
pointer-events: auto;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.lattice .lat-overlay-message {
|
|
495
|
+
padding: calc(var(--lattice-space) * 2) calc(var(--lattice-space) * 3);
|
|
496
|
+
border-radius: var(--lattice-radius);
|
|
497
|
+
background: var(--lattice-surface);
|
|
498
|
+
box-shadow: var(--lattice-popup-shadow);
|
|
499
|
+
color: var(--lattice-foreground-muted);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.lattice .lat-drag-ghost {
|
|
503
|
+
position: absolute;
|
|
504
|
+
z-index: 11;
|
|
505
|
+
padding: 0 var(--lattice-cell-padding-x);
|
|
506
|
+
border: var(--lattice-border-width) solid var(--lattice-accent);
|
|
507
|
+
border-radius: var(--lattice-radius-sm);
|
|
508
|
+
background: var(--lattice-surface);
|
|
509
|
+
box-shadow: var(--lattice-popup-shadow);
|
|
510
|
+
line-height: var(--lattice-header-height);
|
|
511
|
+
opacity: 0.9;
|
|
512
|
+
pointer-events: none;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.lattice .lat-resize-guide {
|
|
516
|
+
position: absolute;
|
|
517
|
+
top: 0;
|
|
518
|
+
z-index: 11;
|
|
519
|
+
width: var(--lattice-border-width);
|
|
520
|
+
height: 100%;
|
|
521
|
+
background: var(--lattice-accent);
|
|
522
|
+
pointer-events: none;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* The fill handle sits at the range corner and is dragged to extend a
|
|
526
|
+
detected series (§12). */
|
|
527
|
+
.lattice .lat-fill-handle {
|
|
528
|
+
position: absolute;
|
|
529
|
+
z-index: 12;
|
|
530
|
+
width: calc(var(--lattice-space) + 2px);
|
|
531
|
+
height: calc(var(--lattice-space) + 2px);
|
|
532
|
+
border: 1px solid var(--lattice-background);
|
|
533
|
+
background: var(--lattice-accent);
|
|
534
|
+
cursor: crosshair;
|
|
535
|
+
pointer-events: auto;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.lattice .lat-status-bar {
|
|
539
|
+
display: flex;
|
|
540
|
+
flex: none;
|
|
541
|
+
align-items: center;
|
|
542
|
+
gap: calc(var(--lattice-space) * 2);
|
|
543
|
+
padding: 0 var(--lattice-cell-padding-x);
|
|
544
|
+
min-height: calc(var(--lattice-row-height) + var(--lattice-space));
|
|
545
|
+
border-top: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
546
|
+
background: var(--lattice-surface);
|
|
547
|
+
color: var(--lattice-foreground-muted);
|
|
548
|
+
font-size: var(--lattice-font-size-sm);
|
|
549
|
+
font-variant-numeric: tabular-nums;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/* ------------------------------------------------------------------ */
|
|
553
|
+
/* Pagination */
|
|
554
|
+
/* ------------------------------------------------------------------ */
|
|
555
|
+
|
|
556
|
+
/* The pager shares the status strip with the count panels, so it takes the
|
|
557
|
+
right-hand end and pushes itself away from them. Without this the two run
|
|
558
|
+
together into one unreadable line of digits — "100 of 100,000 rows" abutting
|
|
559
|
+
"1–100 of 100000" with nothing between them. */
|
|
560
|
+
.lattice .lat-pagination {
|
|
561
|
+
display: flex;
|
|
562
|
+
align-items: center;
|
|
563
|
+
gap: var(--lattice-space);
|
|
564
|
+
margin-left: auto;
|
|
565
|
+
padding-left: calc(var(--lattice-space) * 2);
|
|
566
|
+
border-left: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.lattice .lat-pagination select {
|
|
570
|
+
padding: 2px calc(var(--lattice-space) * 0.5);
|
|
571
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
572
|
+
border-radius: var(--lattice-radius-sm);
|
|
573
|
+
background: var(--lattice-background);
|
|
574
|
+
color: inherit;
|
|
575
|
+
font: inherit;
|
|
576
|
+
font-size: var(--lattice-font-size-sm);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/* The page buttons are a group, so they read as one control rather than four
|
|
580
|
+
loose glyphs. */
|
|
581
|
+
.lattice .lat-pagination button {
|
|
582
|
+
display: inline-flex;
|
|
583
|
+
align-items: center;
|
|
584
|
+
justify-content: center;
|
|
585
|
+
min-width: 1.75em;
|
|
586
|
+
height: 1.75em;
|
|
587
|
+
padding: 0 calc(var(--lattice-space) * 0.5);
|
|
588
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
589
|
+
border-radius: var(--lattice-radius-sm);
|
|
590
|
+
background: var(--lattice-background);
|
|
591
|
+
color: inherit;
|
|
592
|
+
font: inherit;
|
|
593
|
+
cursor: pointer;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.lattice .lat-pagination button:hover:not(:disabled) {
|
|
597
|
+
background: var(--lattice-hovered-background);
|
|
598
|
+
border-color: var(--lattice-border-strong);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.lattice .lat-pagination button:disabled {
|
|
602
|
+
opacity: 0.4;
|
|
603
|
+
cursor: default;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/* The range readout and the page label are text, not controls. */
|
|
607
|
+
.lattice .lat-pagination__summary,
|
|
608
|
+
.lattice .lat-pagination__page {
|
|
609
|
+
white-space: nowrap;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.lattice .lat-status-bar:empty {
|
|
613
|
+
display: none;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* Off-screen measuring container for auto-height rows and column autoSize.
|
|
617
|
+
Not `display: none` — a hidden element has no measurable size. */
|
|
618
|
+
.lattice .lat-measure {
|
|
619
|
+
position: absolute;
|
|
620
|
+
top: 0;
|
|
621
|
+
left: 0;
|
|
622
|
+
visibility: hidden;
|
|
623
|
+
height: auto;
|
|
624
|
+
pointer-events: none;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/* ------------------------------------------------------------------ */
|
|
628
|
+
/* Print */
|
|
629
|
+
/* ------------------------------------------------------------------ */
|
|
630
|
+
|
|
631
|
+
/* Print mode switches virtualisation and pinning off (§14), so the structural
|
|
632
|
+
rules that exist to serve virtualisation have to stand down with it. */
|
|
633
|
+
@media print {
|
|
634
|
+
.lattice .lat-body-viewport { overflow: visible; }
|
|
635
|
+
|
|
636
|
+
.lattice .lat-row,
|
|
637
|
+
.lattice .lat-cell,
|
|
638
|
+
.lattice .lat-body-left-pinned,
|
|
639
|
+
.lattice .lat-body-centre,
|
|
640
|
+
.lattice .lat-body-right-pinned {
|
|
641
|
+
position: static;
|
|
642
|
+
transform: none;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.lattice .lat-row { display: flex; }
|
|
646
|
+
.lattice .lat-cell { flex: 1 1 auto; }
|
|
647
|
+
|
|
648
|
+
.lattice .lat-overlays,
|
|
649
|
+
.lattice .lat-status-bar { display: none; }
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/* ------------------------------------------------------------------ */
|
|
653
|
+
/* Filter popup */
|
|
654
|
+
/* ------------------------------------------------------------------ */
|
|
655
|
+
|
|
656
|
+
/* Rendered into the overlay layer so it escapes the header's overflow
|
|
657
|
+
clipping, and positioned against its header cell (§11.2, §9.2). */
|
|
658
|
+
.lattice .lat-filter-popup {
|
|
659
|
+
position: absolute;
|
|
660
|
+
z-index: 13;
|
|
661
|
+
min-width: 220px;
|
|
662
|
+
max-width: 320px;
|
|
663
|
+
padding: var(--lattice-space);
|
|
664
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
665
|
+
border-radius: var(--lattice-radius);
|
|
666
|
+
background: var(--lattice-surface);
|
|
667
|
+
box-shadow: var(--lattice-popup-shadow);
|
|
668
|
+
color: var(--lattice-foreground);
|
|
669
|
+
font-size: var(--lattice-font-size);
|
|
670
|
+
pointer-events: auto;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/* Text-like inputs fill the popup; checkboxes and radios must keep their
|
|
674
|
+
intrinsic size. A blanket `width: 100%` here stretches every checkbox in the
|
|
675
|
+
set filter to the full popup width and throws its labels out of alignment. */
|
|
676
|
+
.lattice .lat-filter-popup input:not([type='checkbox']):not([type='radio']),
|
|
677
|
+
.lattice .lat-filter-popup select {
|
|
678
|
+
box-sizing: border-box;
|
|
679
|
+
width: 100%;
|
|
680
|
+
padding: calc(var(--lattice-space) * 0.5);
|
|
681
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
682
|
+
border-radius: var(--lattice-radius-sm);
|
|
683
|
+
background: var(--lattice-background);
|
|
684
|
+
color: inherit;
|
|
685
|
+
font: inherit;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.lattice .lat-filter-popup > * + * {
|
|
689
|
+
margin-top: var(--lattice-space);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/* ------------------------------------------------------------------ */
|
|
693
|
+
/* Menus */
|
|
694
|
+
/* ------------------------------------------------------------------ */
|
|
695
|
+
|
|
696
|
+
/* Shared by the column header menu and the cell context menu (§18.5). */
|
|
697
|
+
.lattice .lat-menu {
|
|
698
|
+
position: absolute;
|
|
699
|
+
z-index: 20;
|
|
700
|
+
min-width: 200px;
|
|
701
|
+
padding: calc(var(--lattice-space) * 0.5) 0;
|
|
702
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
703
|
+
border-radius: var(--lattice-radius);
|
|
704
|
+
background: var(--lattice-surface);
|
|
705
|
+
box-shadow: var(--lattice-popup-shadow);
|
|
706
|
+
color: var(--lattice-foreground);
|
|
707
|
+
font-size: var(--lattice-font-size);
|
|
708
|
+
outline: none;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.lattice .lat-menu__item {
|
|
712
|
+
display: flex;
|
|
713
|
+
align-items: center;
|
|
714
|
+
gap: var(--lattice-gap);
|
|
715
|
+
padding: calc(var(--lattice-space) * 0.75) calc(var(--lattice-space) * 1.5);
|
|
716
|
+
cursor: pointer;
|
|
717
|
+
white-space: nowrap;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.lattice .lat-menu__item:hover,
|
|
721
|
+
.lattice .lat-menu__item:focus,
|
|
722
|
+
.lattice .lat-menu__item--focused {
|
|
723
|
+
background: var(--lattice-hovered-background);
|
|
724
|
+
outline: none;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.lattice .lat-menu__item[aria-disabled='true'] {
|
|
728
|
+
color: var(--lattice-foreground-muted);
|
|
729
|
+
cursor: default;
|
|
730
|
+
opacity: 0.55;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.lattice .lat-menu__item[aria-disabled='true']:hover {
|
|
734
|
+
background: transparent;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.lattice .lat-menu__item .lat-icon {
|
|
738
|
+
flex: none;
|
|
739
|
+
width: var(--lattice-icon-size);
|
|
740
|
+
text-align: center;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/* Menu icons are supplied as a name in `data-icon`; the built-in items use a
|
|
744
|
+
character directly, so rendering the attribute covers both. */
|
|
745
|
+
.lattice .lat-menu__item .lat-icon::before {
|
|
746
|
+
content: attr(data-icon);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.lattice .lat-menu__item__label { flex: 1 1 auto; }
|
|
750
|
+
|
|
751
|
+
.lattice .lat-menu__item__shortcut,
|
|
752
|
+
.lattice .lat-menu__item__arrow {
|
|
753
|
+
flex: none;
|
|
754
|
+
color: var(--lattice-foreground-muted);
|
|
755
|
+
font-size: var(--lattice-font-size-sm);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
.lattice .lat-menu__separator {
|
|
759
|
+
height: var(--lattice-border-width);
|
|
760
|
+
margin: calc(var(--lattice-space) * 0.5) 0;
|
|
761
|
+
background: var(--lattice-border-color);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/* ------------------------------------------------------------------ */
|
|
765
|
+
/* Tool panel */
|
|
766
|
+
/* ------------------------------------------------------------------ */
|
|
767
|
+
|
|
768
|
+
/* Docked against the trailing edge. The grid root is padded by the dock's
|
|
769
|
+
width when a panel is open, so the panel sits beside the data rather than
|
|
770
|
+
over it — hiding the rows while choosing which columns to see is exactly
|
|
771
|
+
backwards. Tabs stay visible when closed, so it is always reachable. */
|
|
772
|
+
.lattice .lat-panel-dock {
|
|
773
|
+
position: absolute;
|
|
774
|
+
top: 0;
|
|
775
|
+
right: 0;
|
|
776
|
+
bottom: 0;
|
|
777
|
+
z-index: 5;
|
|
778
|
+
display: flex;
|
|
779
|
+
border-left: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
780
|
+
background: var(--lattice-surface);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.lattice .lat-panel {
|
|
784
|
+
display: flex;
|
|
785
|
+
flex-direction: row-reverse;
|
|
786
|
+
/* Fill the dock. Shrink-to-fit left the filters panel 153px wide inside a
|
|
787
|
+
260px dock, wasting a third of it and wrapping every column name. */
|
|
788
|
+
width: 100%;
|
|
789
|
+
height: 100%;
|
|
790
|
+
font-size: var(--lattice-font-size);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
.lattice .lat-panel__content {
|
|
794
|
+
flex: 1 1 auto;
|
|
795
|
+
min-width: 0;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/* The tab strip runs vertically down the edge, so a closed panel costs only a
|
|
799
|
+
sliver of width. */
|
|
800
|
+
.lattice .lat-panel__tabs {
|
|
801
|
+
display: flex;
|
|
802
|
+
flex: none;
|
|
803
|
+
flex-direction: column;
|
|
804
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
805
|
+
padding: calc(var(--lattice-space) * 0.5);
|
|
806
|
+
border-left: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
807
|
+
background: var(--lattice-surface-alt);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
.lattice .lat-panel__tab {
|
|
811
|
+
padding: var(--lattice-space) calc(var(--lattice-space) * 0.75);
|
|
812
|
+
border: 0;
|
|
813
|
+
border-radius: var(--lattice-radius-sm);
|
|
814
|
+
background: transparent;
|
|
815
|
+
color: var(--lattice-foreground-muted);
|
|
816
|
+
cursor: pointer;
|
|
817
|
+
font: inherit;
|
|
818
|
+
writing-mode: vertical-rl;
|
|
819
|
+
white-space: nowrap;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.lattice .lat-panel__tab:hover { background: var(--lattice-hovered-background); }
|
|
823
|
+
|
|
824
|
+
.lattice .lat-panel__tab[aria-selected='true'] {
|
|
825
|
+
background: var(--lattice-background);
|
|
826
|
+
color: var(--lattice-accent);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.lattice .lat-panel__content {
|
|
830
|
+
overflow: auto;
|
|
831
|
+
/* `min-width: 0` lets the flex item shrink below its content's intrinsic
|
|
832
|
+
width, which is what keeps a long chip from widening the whole dock. */
|
|
833
|
+
flex: 1 1 auto;
|
|
834
|
+
min-width: 0;
|
|
835
|
+
padding: var(--lattice-space);
|
|
836
|
+
background: var(--lattice-background);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
.lattice .lat-panel-dock[data-open='false'] .lat-panel__content { display: none; }
|
|
840
|
+
|
|
841
|
+
/* Where a dragged column will land. A line rather than a highlight, because it
|
|
842
|
+
marks a position between two rows, not a row. */
|
|
843
|
+
.lattice .lat-panel__column[data-drop='before'] {
|
|
844
|
+
box-shadow: inset 0 2px 0 0 var(--lattice-accent);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.lattice .lat-panel__column[data-drop='after'] {
|
|
848
|
+
box-shadow: inset 0 -2px 0 0 var(--lattice-accent);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/* ------------------------------------------------------------------ */
|
|
852
|
+
/* Filters panel */
|
|
853
|
+
/* ------------------------------------------------------------------ */
|
|
854
|
+
|
|
855
|
+
/* The line that answers "what is filtered right now". */
|
|
856
|
+
.lattice .lat-panel__filterSummary {
|
|
857
|
+
display: flex;
|
|
858
|
+
align-items: baseline;
|
|
859
|
+
justify-content: space-between;
|
|
860
|
+
gap: var(--lattice-space);
|
|
861
|
+
padding: calc(var(--lattice-space) * 0.5) 0 var(--lattice-space);
|
|
862
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
863
|
+
margin-bottom: calc(var(--lattice-space) * 0.5);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
.lattice .lat-panel__filterCount {
|
|
867
|
+
font-size: var(--lattice-font-size-sm);
|
|
868
|
+
color: var(--lattice-foreground-muted);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
.lattice .lat-panel__filterCount[data-active='true'] {
|
|
872
|
+
color: var(--lattice-foreground);
|
|
873
|
+
font-weight: 600;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.lattice .lat-panel__clearAll {
|
|
877
|
+
flex: 0 0 auto;
|
|
878
|
+
border: 0;
|
|
879
|
+
padding: 0;
|
|
880
|
+
background: none;
|
|
881
|
+
color: var(--lattice-link-color);
|
|
882
|
+
font: inherit;
|
|
883
|
+
font-size: var(--lattice-font-size-sm);
|
|
884
|
+
cursor: pointer;
|
|
885
|
+
text-decoration: underline;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
.lattice .lat-panel__filters {
|
|
889
|
+
display: flex;
|
|
890
|
+
flex-direction: column;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/* Each column is a row that opens, so it needs a row's affordances: a hit area
|
|
894
|
+
spanning the panel, a twisty, and its state on the right. */
|
|
895
|
+
.lattice .lat-panel__filter {
|
|
896
|
+
position: relative;
|
|
897
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
.lattice .lat-panel__filterTitle {
|
|
901
|
+
display: flex;
|
|
902
|
+
align-items: center;
|
|
903
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
904
|
+
width: 100%;
|
|
905
|
+
box-sizing: border-box;
|
|
906
|
+
/* Room on the right for the clear button, which is absolutely positioned so
|
|
907
|
+
it does not shift the title when it appears. The reservation is the
|
|
908
|
+
button's own width plus its inset, or the "filtered" dot ends up
|
|
909
|
+
underneath it and the two read as one smudged glyph. */
|
|
910
|
+
padding: calc(var(--lattice-space) * 0.75)
|
|
911
|
+
calc(var(--lattice-icon-size) + var(--lattice-space) * 3)
|
|
912
|
+
calc(var(--lattice-space) * 0.75)
|
|
913
|
+
calc(var(--lattice-space) * 0.5);
|
|
914
|
+
border: 0;
|
|
915
|
+
background: none;
|
|
916
|
+
color: inherit;
|
|
917
|
+
font: inherit;
|
|
918
|
+
text-align: left;
|
|
919
|
+
cursor: pointer;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
.lattice .lat-panel__filterTitle:hover { background: var(--lattice-hovered-background); }
|
|
923
|
+
|
|
924
|
+
.lattice .lat-panel__twisty {
|
|
925
|
+
flex: 0 0 auto;
|
|
926
|
+
width: var(--lattice-icon-size);
|
|
927
|
+
height: var(--lattice-icon-size);
|
|
928
|
+
color: var(--lattice-foreground-muted);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.lattice .lat-panel__filterName {
|
|
932
|
+
flex: 1 1 auto;
|
|
933
|
+
min-width: 0;
|
|
934
|
+
overflow: hidden;
|
|
935
|
+
text-overflow: ellipsis;
|
|
936
|
+
white-space: nowrap;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/* A filtered column is named in bold and marked with a dot: two signals, so
|
|
940
|
+
neither colour nor weight alone has to carry it (§17). */
|
|
941
|
+
.lattice .lat-panel__filter[data-filtered='true'] .lat-panel__filterName {
|
|
942
|
+
font-weight: 600;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.lattice .lat-panel__filterDot {
|
|
946
|
+
flex: 0 0 auto;
|
|
947
|
+
margin-left: calc(var(--lattice-space) * 0.5);
|
|
948
|
+
width: var(--lattice-dot-size);
|
|
949
|
+
height: var(--lattice-dot-size);
|
|
950
|
+
border-radius: var(--lattice-radius-pill);
|
|
951
|
+
background: var(--lattice-accent);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
.lattice .lat-panel__filterClear {
|
|
955
|
+
position: absolute;
|
|
956
|
+
top: calc(var(--lattice-space) * 0.5);
|
|
957
|
+
right: calc(var(--lattice-space) * 0.5);
|
|
958
|
+
display: inline-flex;
|
|
959
|
+
align-items: center;
|
|
960
|
+
justify-content: center;
|
|
961
|
+
width: calc(var(--lattice-icon-size) + var(--lattice-space));
|
|
962
|
+
height: calc(var(--lattice-icon-size) + var(--lattice-space));
|
|
963
|
+
padding: 0;
|
|
964
|
+
border: 0;
|
|
965
|
+
border-radius: var(--lattice-radius-sm);
|
|
966
|
+
background: none;
|
|
967
|
+
color: var(--lattice-foreground-muted);
|
|
968
|
+
cursor: pointer;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.lattice .lat-panel__filterClear:hover {
|
|
972
|
+
background: var(--lattice-hovered-background);
|
|
973
|
+
color: var(--lattice-variant-danger-text);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.lattice .lat-panel__filterBody {
|
|
977
|
+
padding: 0 calc(var(--lattice-space) * 0.5) var(--lattice-space);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
.lattice .lat-panel__empty {
|
|
981
|
+
margin: var(--lattice-space) 0;
|
|
982
|
+
font-size: var(--lattice-font-size-sm);
|
|
983
|
+
color: var(--lattice-foreground-muted);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
.lattice .lat-panel__search {
|
|
987
|
+
box-sizing: border-box;
|
|
988
|
+
width: 100%;
|
|
989
|
+
margin-bottom: var(--lattice-space);
|
|
990
|
+
padding: calc(var(--lattice-space) * 0.5);
|
|
991
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
992
|
+
border-radius: var(--lattice-radius-sm);
|
|
993
|
+
background: var(--lattice-background);
|
|
994
|
+
color: inherit;
|
|
995
|
+
font: inherit;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/* Drop zones for grouping, values and pivot (§8.10). */
|
|
999
|
+
/* A drop target has to look like one before anything is dropped on it, so the
|
|
1000
|
+
dashed edge is deliberately stronger than a decorative border would be. */
|
|
1001
|
+
.lattice .lat-panel__zone {
|
|
1002
|
+
min-height: calc(var(--lattice-row-height) * 1.8);
|
|
1003
|
+
margin-bottom: var(--lattice-space);
|
|
1004
|
+
padding: calc(var(--lattice-space) * 0.75);
|
|
1005
|
+
border: 1px dashed var(--lattice-border-strong);
|
|
1006
|
+
border-radius: var(--lattice-radius);
|
|
1007
|
+
background: var(--lattice-surface-alt);
|
|
1008
|
+
transition: border-color var(--lattice-transition), background var(--lattice-transition);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/* Always shown under the zone title: what this zone is for. */
|
|
1012
|
+
.lattice .lat-panel__zoneAbout {
|
|
1013
|
+
margin-bottom: calc(var(--lattice-space) * 0.5);
|
|
1014
|
+
color: var(--lattice-foreground-muted);
|
|
1015
|
+
font-size: var(--lattice-font-size-sm);
|
|
1016
|
+
line-height: 1.35;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/* Shown only while the zone is empty; it says what dropping here will do. */
|
|
1020
|
+
.lattice .lat-panel__zoneHint {
|
|
1021
|
+
display: block;
|
|
1022
|
+
color: var(--lattice-foreground-muted);
|
|
1023
|
+
font-size: var(--lattice-font-size-sm);
|
|
1024
|
+
line-height: 1.35;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.lattice .lat-panel__zoneBody {
|
|
1028
|
+
display: flex;
|
|
1029
|
+
flex-wrap: wrap;
|
|
1030
|
+
gap: calc(var(--lattice-space) * 0.25);
|
|
1031
|
+
min-height: var(--lattice-row-height);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
.lattice .lat-panel__zone[data-over='true'] {
|
|
1035
|
+
border-color: var(--lattice-accent);
|
|
1036
|
+
border-style: solid;
|
|
1037
|
+
background: var(--lattice-hovered-background);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
.lattice .lat-panel__zoneTitle {
|
|
1041
|
+
display: block;
|
|
1042
|
+
margin-bottom: calc(var(--lattice-space) * 0.5);
|
|
1043
|
+
color: var(--lattice-foreground-muted);
|
|
1044
|
+
font-size: var(--lattice-font-size-sm);
|
|
1045
|
+
text-transform: uppercase;
|
|
1046
|
+
letter-spacing: 0.04em;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
.lattice .lat-panel__zoneBody .lat-chip {
|
|
1050
|
+
display: inline-flex;
|
|
1051
|
+
align-items: center;
|
|
1052
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
1053
|
+
margin: calc(var(--lattice-space) * 0.25);
|
|
1054
|
+
padding: calc(var(--lattice-space) * 0.25) calc(var(--lattice-space) * 0.75);
|
|
1055
|
+
border-radius: var(--lattice-radius-pill);
|
|
1056
|
+
background: var(--lattice-variant-info-fill, var(--lattice-hovered-background));
|
|
1057
|
+
cursor: grab;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
.lattice .lat-panel__zoneBody .lat-chip__remove {
|
|
1061
|
+
border: 0;
|
|
1062
|
+
background: transparent;
|
|
1063
|
+
color: inherit;
|
|
1064
|
+
cursor: pointer;
|
|
1065
|
+
font: inherit;
|
|
1066
|
+
line-height: 1;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
.lattice .lat-panel__column {
|
|
1070
|
+
display: flex;
|
|
1071
|
+
align-items: center;
|
|
1072
|
+
gap: var(--lattice-gap);
|
|
1073
|
+
padding: calc(var(--lattice-space) * 0.5) calc(var(--lattice-space) * 0.25);
|
|
1074
|
+
border-radius: var(--lattice-radius-sm);
|
|
1075
|
+
cursor: grab;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
.lattice .lat-panel__column:hover { background: var(--lattice-hovered-background); }
|
|
1079
|
+
|
|
1080
|
+
.lattice .lat-panel__filter {
|
|
1081
|
+
margin-bottom: calc(var(--lattice-space) * 0.5);
|
|
1082
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
.lattice .lat-panel__filter summary,
|
|
1086
|
+
.lattice .lat-panel__filter-title {
|
|
1087
|
+
padding: calc(var(--lattice-space) * 0.5) 0;
|
|
1088
|
+
cursor: pointer;
|
|
1089
|
+
font-weight: 600;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/* ------------------------------------------------------------------ */
|
|
1093
|
+
/* Set filter and virtualised option lists */
|
|
1094
|
+
/* ------------------------------------------------------------------ */
|
|
1095
|
+
|
|
1096
|
+
/* The list is virtualised (§9.2: a 20,000-entry list must open instantly), so
|
|
1097
|
+
its rows are absolutely positioned and moved by transform. Only the row's
|
|
1098
|
+
internal layout belongs here. */
|
|
1099
|
+
.lattice .lat-list__viewport {
|
|
1100
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1101
|
+
border-radius: var(--lattice-radius-sm);
|
|
1102
|
+
background: var(--lattice-background);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.lattice .lat-list__option {
|
|
1106
|
+
display: flex;
|
|
1107
|
+
align-items: center;
|
|
1108
|
+
gap: var(--lattice-gap);
|
|
1109
|
+
/* Roomier than a grid cell on purpose. A row here is a click target being
|
|
1110
|
+
chosen from a list, not data being scanned in bulk, so it gets the padding
|
|
1111
|
+
a menu item would rather than the density the grid body needs. */
|
|
1112
|
+
padding: 0 calc(var(--lattice-space) * 1.5);
|
|
1113
|
+
cursor: pointer;
|
|
1114
|
+
white-space: nowrap;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
.lattice .lat-list__option:hover,
|
|
1118
|
+
.lattice .lat-list__option--active {
|
|
1119
|
+
background: var(--lattice-hovered-background);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
.lattice .lat-list__option--selected {
|
|
1123
|
+
background: var(--lattice-selected-background);
|
|
1124
|
+
color: var(--lattice-selected-foreground);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
.lattice .lat-list__option--disabled {
|
|
1128
|
+
color: var(--lattice-foreground-muted);
|
|
1129
|
+
cursor: default;
|
|
1130
|
+
opacity: 0.55;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
.lattice .lat-list__option__label {
|
|
1134
|
+
overflow: hidden;
|
|
1135
|
+
flex: 1 1 auto;
|
|
1136
|
+
text-overflow: ellipsis;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/* Hierarchical set values for tree columns (§9.2), indented by depth. */
|
|
1140
|
+
.lattice .lat-list__option[data-depth='1'] { padding-left: calc(var(--lattice-space) * 3.25); }
|
|
1141
|
+
.lattice .lat-list__option[data-depth='2'] { padding-left: calc(var(--lattice-space) * 4.75); }
|
|
1142
|
+
.lattice .lat-list__option[data-depth='3'] { padding-left: calc(var(--lattice-space) * 6.25); }
|
|
1143
|
+
|
|
1144
|
+
/* "Select all" and "Clear" are actions; the summary beside them is a readout of
|
|
1145
|
+
what is currently selected, so it must not look like a third button. */
|
|
1146
|
+
.lattice .lat-filter__actions {
|
|
1147
|
+
display: flex;
|
|
1148
|
+
align-items: center;
|
|
1149
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
.lattice .lat-filter__all,
|
|
1153
|
+
.lattice .lat-filter__none {
|
|
1154
|
+
padding: calc(var(--lattice-space) * 0.4) calc(var(--lattice-space) * 0.9);
|
|
1155
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1156
|
+
border-radius: var(--lattice-radius-sm);
|
|
1157
|
+
background: var(--lattice-surface);
|
|
1158
|
+
color: inherit;
|
|
1159
|
+
cursor: pointer;
|
|
1160
|
+
font: inherit;
|
|
1161
|
+
font-size: var(--lattice-font-size-sm);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
.lattice .lat-filter__all:hover,
|
|
1165
|
+
.lattice .lat-filter__none:hover {
|
|
1166
|
+
background: var(--lattice-hovered-background);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
.lattice .lat-filter__summary {
|
|
1170
|
+
margin-left: auto;
|
|
1171
|
+
color: var(--lattice-foreground-muted);
|
|
1172
|
+
font-size: var(--lattice-font-size-sm);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/* The two-condition shell: operator select above its value inputs (§9.2). */
|
|
1176
|
+
.lattice .lat-filter__condition {
|
|
1177
|
+
display: flex;
|
|
1178
|
+
flex-direction: column;
|
|
1179
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
.lattice .lat-filter__join {
|
|
1183
|
+
display: flex;
|
|
1184
|
+
gap: var(--lattice-gap);
|
|
1185
|
+
align-items: center;
|
|
1186
|
+
color: var(--lattice-foreground-muted);
|
|
1187
|
+
font-size: var(--lattice-font-size-sm);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/* Section heading above the column list, with its bulk actions. */
|
|
1191
|
+
.lattice .lat-panel__heading {
|
|
1192
|
+
display: flex;
|
|
1193
|
+
align-items: baseline;
|
|
1194
|
+
gap: var(--lattice-gap);
|
|
1195
|
+
margin: var(--lattice-space) 0 calc(var(--lattice-space) * 0.5);
|
|
1196
|
+
padding-top: var(--lattice-space);
|
|
1197
|
+
border-top: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1198
|
+
color: var(--lattice-foreground-muted);
|
|
1199
|
+
font-size: var(--lattice-font-size-sm);
|
|
1200
|
+
text-transform: uppercase;
|
|
1201
|
+
letter-spacing: 0.04em;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
.lattice .lat-panel__heading > span { flex: 1 1 auto; }
|
|
1205
|
+
|
|
1206
|
+
.lattice .lat-panel__link {
|
|
1207
|
+
padding: 0;
|
|
1208
|
+
border: 0;
|
|
1209
|
+
background: none;
|
|
1210
|
+
color: var(--lattice-link-color, var(--lattice-accent));
|
|
1211
|
+
cursor: pointer;
|
|
1212
|
+
font: inherit;
|
|
1213
|
+
text-transform: none;
|
|
1214
|
+
letter-spacing: normal;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.lattice .lat-panel__link:hover { text-decoration: underline; }
|
|
1218
|
+
|
|
1219
|
+
/* The drag grip: six dots, drawn rather than shipped as an asset. */
|
|
1220
|
+
.lattice .lat-panel__grip {
|
|
1221
|
+
flex: none;
|
|
1222
|
+
width: 10px;
|
|
1223
|
+
height: 14px;
|
|
1224
|
+
background-image: radial-gradient(var(--lattice-border-strong) 1px, transparent 1px);
|
|
1225
|
+
background-size: 4px 4px;
|
|
1226
|
+
background-position: 1px 2px;
|
|
1227
|
+
opacity: 0.7;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.lattice .lat-panel__column:hover .lat-panel__grip { opacity: 1; }
|
|
1231
|
+
.lattice .lat-panel__column:active { cursor: grabbing; }
|
|
1232
|
+
|
|
1233
|
+
/* The zone's chips carry the column name and a remove control. */
|
|
1234
|
+
.lattice .lat-panel__zoneBody .lat-chip {
|
|
1235
|
+
display: inline-flex;
|
|
1236
|
+
align-items: center;
|
|
1237
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
1238
|
+
padding: calc(var(--lattice-space) * 0.25) calc(var(--lattice-space) * 0.75);
|
|
1239
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1240
|
+
border-radius: var(--lattice-radius-pill);
|
|
1241
|
+
background: var(--lattice-background);
|
|
1242
|
+
font-size: var(--lattice-font-size-sm);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
.lattice .lat-panel__zoneBody .lat-chip__remove {
|
|
1246
|
+
border: 0;
|
|
1247
|
+
border-radius: 50%;
|
|
1248
|
+
background: transparent;
|
|
1249
|
+
color: var(--lattice-foreground-muted);
|
|
1250
|
+
cursor: pointer;
|
|
1251
|
+
font: inherit;
|
|
1252
|
+
line-height: 1;
|
|
1253
|
+
padding: 0 2px;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
.lattice .lat-panel__zoneBody .lat-chip__remove:hover {
|
|
1257
|
+
background: var(--lattice-hovered-background);
|
|
1258
|
+
color: var(--lattice-foreground);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/* The reduction chooser on a Values chip (§9.4). */
|
|
1262
|
+
.lattice .lat-chip__fn {
|
|
1263
|
+
border: 0;
|
|
1264
|
+
border-radius: var(--lattice-radius-sm);
|
|
1265
|
+
background: var(--lattice-surface-alt);
|
|
1266
|
+
color: var(--lattice-foreground-muted);
|
|
1267
|
+
cursor: pointer;
|
|
1268
|
+
font: inherit;
|
|
1269
|
+
font-size: var(--lattice-font-size-sm);
|
|
1270
|
+
padding: 0 2px;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
.lattice .lat-chip__fn:hover { color: var(--lattice-foreground); }
|
|
1274
|
+
|
|
1275
|
+
/* The grouped-row-calculation zone: one full-width row per column, so the
|
|
1276
|
+
calculation choosers line up instead of stepping across the panel. */
|
|
1277
|
+
.lattice .lat-panel__zoneBody--rows {
|
|
1278
|
+
display: block;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
.lattice .lat-chip--row {
|
|
1282
|
+
display: flex;
|
|
1283
|
+
align-items: center;
|
|
1284
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
1285
|
+
width: 100%;
|
|
1286
|
+
margin: 2px 0;
|
|
1287
|
+
padding: calc(var(--lattice-space) * 0.35) calc(var(--lattice-space) * 0.6);
|
|
1288
|
+
border-radius: var(--lattice-radius-sm);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
.lattice .lat-chip__label {
|
|
1292
|
+
overflow: hidden;
|
|
1293
|
+
text-overflow: ellipsis;
|
|
1294
|
+
white-space: nowrap;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
.lattice .lat-chip--row .lat-chip__label { flex: 1 1 auto; }
|
|
1298
|
+
|
|
1299
|
+
/* A native select inside a pill is visually loud. Strip its chrome and draw a
|
|
1300
|
+
small caret, so the calculation reads as part of the row. */
|
|
1301
|
+
.lattice .lat-chip__fn {
|
|
1302
|
+
appearance: none;
|
|
1303
|
+
-webkit-appearance: none;
|
|
1304
|
+
flex: none;
|
|
1305
|
+
max-width: 84px;
|
|
1306
|
+
padding: 1px 14px 1px 6px;
|
|
1307
|
+
border: var(--lattice-border-width) solid transparent;
|
|
1308
|
+
border-radius: var(--lattice-radius-sm);
|
|
1309
|
+
background-color: var(--lattice-surface-alt);
|
|
1310
|
+
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
|
1311
|
+
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
|
1312
|
+
background-position: calc(100% - 8px) 55%, calc(100% - 5px) 55%;
|
|
1313
|
+
background-size: 3px 3px, 3px 3px;
|
|
1314
|
+
background-repeat: no-repeat;
|
|
1315
|
+
color: var(--lattice-foreground-muted);
|
|
1316
|
+
cursor: pointer;
|
|
1317
|
+
font: inherit;
|
|
1318
|
+
font-size: var(--lattice-font-size-sm);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
.lattice .lat-chip__fn:hover,
|
|
1322
|
+
.lattice .lat-chip__fn:focus {
|
|
1323
|
+
border-color: var(--lattice-border-color);
|
|
1324
|
+
color: var(--lattice-foreground);
|
|
1325
|
+
outline: none;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/* A pinned total row sits outside the scrolling body, so it needs its own
|
|
1329
|
+
surface and a rule separating it from the rows above (§9.4). */
|
|
1330
|
+
.lattice .lat-sticky-bottom .lat-row {
|
|
1331
|
+
position: relative;
|
|
1332
|
+
height: var(--lattice-row-height);
|
|
1333
|
+
border-bottom: 0;
|
|
1334
|
+
background: var(--lattice-surface);
|
|
1335
|
+
font-weight: 600;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
.lattice .lat-sticky-bottom {
|
|
1339
|
+
border-top: var(--lattice-border-width) solid var(--lattice-border-strong);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/* ------------------------------------------------------------------ */
|
|
1343
|
+
/* Text selection */
|
|
1344
|
+
/* ------------------------------------------------------------------ */
|
|
1345
|
+
|
|
1346
|
+
/*
|
|
1347
|
+
* The grid body does not do native text selection.
|
|
1348
|
+
*
|
|
1349
|
+
* Dragging across cells is a *range* gesture, and the browser answering it by
|
|
1350
|
+
* highlighting the text as well gives two overlapping selections in two
|
|
1351
|
+
* different blues — one of which the grid knows nothing about. Copy is not lost
|
|
1352
|
+
* by turning it off: the range is what Ctrl+C copies, and it copies as cells
|
|
1353
|
+
* rather than as a run of text, which is what a spreadsheet paste needs.
|
|
1354
|
+
*
|
|
1355
|
+
* Anywhere the user is meant to be working *with* text opts back in below.
|
|
1356
|
+
*/
|
|
1357
|
+
.lattice .lat-body-viewport,
|
|
1358
|
+
.lattice .lat-header,
|
|
1359
|
+
.lattice .lat-sticky-top,
|
|
1360
|
+
.lattice .lat-sticky-bottom {
|
|
1361
|
+
user-select: none;
|
|
1362
|
+
-webkit-user-select: none;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/* Editors, popups and any real form control keep normal text behaviour —
|
|
1366
|
+
selecting inside the value you are typing is the whole point of being in an
|
|
1367
|
+
editor, and a native input with `user-select: none` still cannot be
|
|
1368
|
+
double-clicked to select a word. */
|
|
1369
|
+
.lattice .lat-editor-host,
|
|
1370
|
+
.lattice .lat-editor--popup,
|
|
1371
|
+
.lattice .lat-panel,
|
|
1372
|
+
.lattice .lat-menu,
|
|
1373
|
+
.lattice input,
|
|
1374
|
+
.lattice textarea,
|
|
1375
|
+
.lattice [contenteditable='true'] {
|
|
1376
|
+
user-select: text;
|
|
1377
|
+
-webkit-user-select: text;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
/* ------------------------------------------------------------------ */
|
|
1381
|
+
/* Editing */
|
|
1382
|
+
/* ------------------------------------------------------------------ */
|
|
1383
|
+
|
|
1384
|
+
/* The editor replaces the cell's content, so it inherits the cell's width and
|
|
1385
|
+
alignment. Padding is removed because the input supplies its own. */
|
|
1386
|
+
/* An inset shadow is drawn inside the padding box and then clipped by the
|
|
1387
|
+
cell's own `overflow: hidden`, so its edges disappear. An outline with a
|
|
1388
|
+
negative offset is painted over the border box and survives, and the raised
|
|
1389
|
+
stacking keeps the neighbouring cell from covering it. */
|
|
1390
|
+
.lattice .lat-editor-host {
|
|
1391
|
+
z-index: 3;
|
|
1392
|
+
padding: 0;
|
|
1393
|
+
overflow: visible;
|
|
1394
|
+
outline: 2px solid var(--lattice-accent);
|
|
1395
|
+
outline-offset: -2px;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/* Text-like controls fill the cell; checkboxes and radios must keep their
|
|
1399
|
+
intrinsic size. A blanket `width: 100%` stretched a boolean column's
|
|
1400
|
+
checkbox to the full cell — 67px wide and 27 tall — which reads as a large
|
|
1401
|
+
empty box rather than a tick, and is a hit target the size of the cell for a
|
|
1402
|
+
control that is 13px square. The same exception already exists for the edit
|
|
1403
|
+
bar's slot and the filter popup. */
|
|
1404
|
+
.lattice .lat-editor-host input:not([type='checkbox']):not([type='radio']),
|
|
1405
|
+
.lattice .lat-editor-host select,
|
|
1406
|
+
.lattice .lat-editor-host textarea {
|
|
1407
|
+
box-sizing: border-box;
|
|
1408
|
+
width: 100%;
|
|
1409
|
+
height: 100%;
|
|
1410
|
+
padding: 0 var(--lattice-cell-padding-x);
|
|
1411
|
+
border: 0;
|
|
1412
|
+
background: var(--lattice-background);
|
|
1413
|
+
color: inherit;
|
|
1414
|
+
font: inherit;
|
|
1415
|
+
outline: none;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
.lattice .lat-editor-host .lat-cell--end input,
|
|
1419
|
+
.lattice .lat-cell--end.lat-editor-host input { text-align: right; }
|
|
1420
|
+
|
|
1421
|
+
/* A boolean editor is one control in the middle of the cell, not a field. */
|
|
1422
|
+
.lattice .lat-editor-host input[type='checkbox'],
|
|
1423
|
+
.lattice .lat-editor-host input[type='radio'] {
|
|
1424
|
+
display: block;
|
|
1425
|
+
width: auto;
|
|
1426
|
+
height: 100%;
|
|
1427
|
+
margin: 0 auto;
|
|
1428
|
+
accent-color: var(--lattice-accent);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/* An invalid commit takes a state class and keeps the editor open (§11.2). */
|
|
1432
|
+
.lattice .lat-invalid,
|
|
1433
|
+
.lattice .lat-editor-host.lat-invalid {
|
|
1434
|
+
box-shadow: inset 0 0 0 2px var(--lattice-invalid-border);
|
|
1435
|
+
background: var(--lattice-invalid-background);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/* The optional spreadsheet-style bar, above the header. */
|
|
1439
|
+
.lattice .lat-editor-bar {
|
|
1440
|
+
display: flex;
|
|
1441
|
+
flex: none;
|
|
1442
|
+
align-items: stretch;
|
|
1443
|
+
gap: var(--lattice-gap);
|
|
1444
|
+
padding: calc(var(--lattice-space) * 0.5);
|
|
1445
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1446
|
+
background: var(--lattice-surface);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
.lattice .lat-editor-bar__ref {
|
|
1450
|
+
display: flex;
|
|
1451
|
+
flex: none;
|
|
1452
|
+
align-items: center;
|
|
1453
|
+
justify-content: flex-end;
|
|
1454
|
+
min-width: 120px;
|
|
1455
|
+
max-width: 180px;
|
|
1456
|
+
overflow: hidden;
|
|
1457
|
+
padding: 0 var(--lattice-space);
|
|
1458
|
+
border-right: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1459
|
+
color: var(--lattice-foreground-muted);
|
|
1460
|
+
font-size: var(--lattice-font-size-sm);
|
|
1461
|
+
text-overflow: ellipsis;
|
|
1462
|
+
white-space: nowrap;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
.lattice .lat-editor-bar__input {
|
|
1466
|
+
flex: 1 1 auto;
|
|
1467
|
+
min-width: 0;
|
|
1468
|
+
padding: calc(var(--lattice-space) * 0.4) var(--lattice-space);
|
|
1469
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1470
|
+
border-radius: var(--lattice-radius-sm);
|
|
1471
|
+
background: var(--lattice-background);
|
|
1472
|
+
color: inherit;
|
|
1473
|
+
font: inherit;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
.lattice .lat-editor-bar__input:disabled {
|
|
1477
|
+
background: var(--lattice-surface-alt);
|
|
1478
|
+
color: var(--lattice-foreground-muted);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
.lattice .lat-editor-bar__input:focus {
|
|
1482
|
+
border-color: var(--lattice-accent);
|
|
1483
|
+
outline: none;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/* A popup editor is mounted into the overlay layer, outside the cell that
|
|
1487
|
+
spawned it, so it needs its own surface — without one it renders as bare
|
|
1488
|
+
text floating over the rows beneath. */
|
|
1489
|
+
.lattice .lat-editor--popup {
|
|
1490
|
+
z-index: 14;
|
|
1491
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1492
|
+
border-radius: var(--lattice-radius);
|
|
1493
|
+
background: var(--lattice-surface);
|
|
1494
|
+
box-shadow: var(--lattice-popup-shadow);
|
|
1495
|
+
overflow: hidden;
|
|
1496
|
+
/*
|
|
1497
|
+
* Opt back into hit testing. `.lat-overlays` is `pointer-events: none` so a
|
|
1498
|
+
* drag ghost cannot swallow a click, and every overlay that is meant to be
|
|
1499
|
+
* *used* has to opt back in — the loading overlay and the filter popup both
|
|
1500
|
+
* do. Without this a popup editor is drawn but transparent to the pointer:
|
|
1501
|
+
* clicking an option or a calendar day passes straight through to the grid
|
|
1502
|
+
* cell underneath, which then opens an editor of its own. That is a
|
|
1503
|
+
* completely dead dropdown, and it looks like a mis-targeted click rather
|
|
1504
|
+
* than a missing CSS declaration.
|
|
1505
|
+
*/
|
|
1506
|
+
pointer-events: auto;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
.lattice .lat-editor--popup .lat-list__viewport {
|
|
1510
|
+
border: 0;
|
|
1511
|
+
border-radius: 0;
|
|
1512
|
+
padding: calc(var(--lattice-space) * 0.5) 0;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/*
|
|
1516
|
+
* The filter row at the top of a select popup.
|
|
1517
|
+
*
|
|
1518
|
+
* Full bleed with a single dividing rule underneath — no radius and no inset.
|
|
1519
|
+
* A rounded box sitting inside the popup's own rounded corner reads as a
|
|
1520
|
+
* separate floating control, and the eye spends a moment deciding which of the
|
|
1521
|
+
* two boxes it is in. The row is part of the popup's frame, so it is drawn as
|
|
1522
|
+
* frame: square, edge to edge, one line separating it from the list.
|
|
1523
|
+
*/
|
|
1524
|
+
.lattice .lat-editor__search {
|
|
1525
|
+
display: flex;
|
|
1526
|
+
align-items: center;
|
|
1527
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
1528
|
+
padding-right: var(--lattice-space);
|
|
1529
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1530
|
+
background: var(--lattice-background);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
.lattice .lat-editor--popup .lat-editor__search input[type='search'],
|
|
1534
|
+
.lattice .lat-editor--popup .lat-editor__search input[type='text'],
|
|
1535
|
+
.lattice .lat-editor--popup input[type='search'],
|
|
1536
|
+
.lattice .lat-editor--popup input[type='text'] {
|
|
1537
|
+
box-sizing: border-box;
|
|
1538
|
+
width: 100%;
|
|
1539
|
+
min-width: 0;
|
|
1540
|
+
flex: 1 1 auto;
|
|
1541
|
+
padding: calc(var(--lattice-space) * 0.5) var(--lattice-space);
|
|
1542
|
+
border: 0;
|
|
1543
|
+
border-radius: 0;
|
|
1544
|
+
background: transparent;
|
|
1545
|
+
color: inherit;
|
|
1546
|
+
font: inherit;
|
|
1547
|
+
outline: none;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/* The input inside the row already carries the dividing rule. */
|
|
1551
|
+
.lattice .lat-editor--popup .lat-editor__search input { border-bottom: 0; }
|
|
1552
|
+
|
|
1553
|
+
/* The funnel: this box narrows the list, it does not hold the value. Muted,
|
|
1554
|
+
because it is a label for the field rather than a control to press. */
|
|
1555
|
+
.lattice .lat-editor__search-icon {
|
|
1556
|
+
display: inline-flex;
|
|
1557
|
+
flex: 0 0 auto;
|
|
1558
|
+
align-items: center;
|
|
1559
|
+
color: var(--lattice-foreground-muted);
|
|
1560
|
+
pointer-events: none;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
/* Focus belongs to the whole row, since the glyph is part of the field. */
|
|
1564
|
+
.lattice .lat-editor__search:focus-within {
|
|
1565
|
+
box-shadow: inset 0 -2px 0 0 var(--lattice-accent);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
.lattice .lat-editor__search:focus-within .lat-editor__search-icon {
|
|
1569
|
+
color: var(--lattice-accent);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
/* The bar hosts the column's real editor, so the slot must size like an input
|
|
1573
|
+
without dictating what goes in it. */
|
|
1574
|
+
.lattice .lat-editor-bar__slot {
|
|
1575
|
+
display: flex;
|
|
1576
|
+
flex: 1 1 auto;
|
|
1577
|
+
align-items: center;
|
|
1578
|
+
min-width: 0;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
.lattice .lat-editor-bar__slot > * {
|
|
1582
|
+
box-sizing: border-box;
|
|
1583
|
+
flex: 1 1 auto;
|
|
1584
|
+
min-width: 0;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
.lattice .lat-editor-bar__slot input:not([type='checkbox']):not([type='radio']),
|
|
1588
|
+
.lattice .lat-editor-bar__slot select,
|
|
1589
|
+
.lattice .lat-editor-bar__slot textarea {
|
|
1590
|
+
width: 100%;
|
|
1591
|
+
padding: calc(var(--lattice-space) * 0.4) var(--lattice-space);
|
|
1592
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1593
|
+
border-radius: var(--lattice-radius-sm);
|
|
1594
|
+
background: var(--lattice-background);
|
|
1595
|
+
color: inherit;
|
|
1596
|
+
font: inherit;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
.lattice .lat-editor-bar__slot input:focus,
|
|
1600
|
+
.lattice .lat-editor-bar__slot select:focus {
|
|
1601
|
+
border-color: var(--lattice-accent);
|
|
1602
|
+
outline: none;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
/* A read-only column still shows its value, so the bar never blanks out. */
|
|
1606
|
+
.lattice .lat-editor-bar__readonly {
|
|
1607
|
+
flex: 1 1 auto;
|
|
1608
|
+
padding: calc(var(--lattice-space) * 0.4) var(--lattice-space);
|
|
1609
|
+
color: var(--lattice-foreground-muted);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/* ------------------------------------------------------------------ */
|
|
1613
|
+
/* The action rail (§18.6) */
|
|
1614
|
+
/* ------------------------------------------------------------------ */
|
|
1615
|
+
|
|
1616
|
+
/* Docking against the leading edge. Every offset the right-hand dock sets has
|
|
1617
|
+
to be unset, not just overridden, or the panel is stretched across the whole
|
|
1618
|
+
grid by having both `left` and `right` resolved. */
|
|
1619
|
+
.lattice .lat-panel-dock--left {
|
|
1620
|
+
right: auto;
|
|
1621
|
+
left: 0;
|
|
1622
|
+
border-right: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1623
|
+
border-left: 0;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
/* The strip sits inboard of the body on the right and outboard on the left, so
|
|
1627
|
+
the icons are always against the grid's outer edge and the panel always opens
|
|
1628
|
+
towards the data. */
|
|
1629
|
+
.lattice .lat-panel--left { flex-direction: row; }
|
|
1630
|
+
|
|
1631
|
+
.lattice .lat-panel--left .lat-panel__tabs {
|
|
1632
|
+
border-right: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1633
|
+
border-left: 0;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
/* Icon mode. The strip becomes a column of square buttons: the label moves to
|
|
1637
|
+
the tooltip, so the vertical writing mode that made the text strip readable
|
|
1638
|
+
is actively wrong here and has to be turned off. */
|
|
1639
|
+
.lattice .lat-panel--icons .lat-panel__tab,
|
|
1640
|
+
.lattice .lat-panel--icons .lat-panel__action {
|
|
1641
|
+
display: flex;
|
|
1642
|
+
align-items: center;
|
|
1643
|
+
justify-content: center;
|
|
1644
|
+
width: 28px;
|
|
1645
|
+
height: 28px;
|
|
1646
|
+
padding: 0;
|
|
1647
|
+
writing-mode: horizontal-tb;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
.lattice .lat-panel__action {
|
|
1651
|
+
border: 0;
|
|
1652
|
+
border-radius: var(--lattice-radius-sm);
|
|
1653
|
+
background: transparent;
|
|
1654
|
+
color: var(--lattice-foreground-muted);
|
|
1655
|
+
cursor: pointer;
|
|
1656
|
+
font: inherit;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
.lattice .lat-panel__action:hover:not(:disabled) { background: var(--lattice-hovered-background); }
|
|
1660
|
+
|
|
1661
|
+
/* A disabled action stays visible rather than disappearing: the rail's shape
|
|
1662
|
+
should not change as the user works, or the button they are reaching for
|
|
1663
|
+
moves out from under the pointer. */
|
|
1664
|
+
.lattice .lat-panel__action:disabled {
|
|
1665
|
+
opacity: 0.35;
|
|
1666
|
+
cursor: default;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
.lattice .lat-panel__icon {
|
|
1670
|
+
width: 16px;
|
|
1671
|
+
height: 16px;
|
|
1672
|
+
pointer-events: none;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
/* Actions and panel toggles are different kinds of control, so they are
|
|
1676
|
+
separated by a rule rather than only by proximity. */
|
|
1677
|
+
.lattice .lat-panel__actions {
|
|
1678
|
+
display: flex;
|
|
1679
|
+
flex-direction: column;
|
|
1680
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
1681
|
+
margin-bottom: calc(var(--lattice-space) * 0.5);
|
|
1682
|
+
padding-bottom: calc(var(--lattice-space) * 0.5);
|
|
1683
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
.lattice .lat-panel__actions:empty {
|
|
1687
|
+
margin: 0;
|
|
1688
|
+
padding: 0;
|
|
1689
|
+
border: 0;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
/* The quick-filter panel's match count. */
|
|
1693
|
+
.lattice .lat-panel__count {
|
|
1694
|
+
margin: calc(var(--lattice-space) * 0.5) 0 0;
|
|
1695
|
+
color: var(--lattice-foreground-muted);
|
|
1696
|
+
font-size: var(--lattice-font-size-sm);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
|
|
1700
|
+
/*
|
|
1701
|
+
* Lattice Grid — editor and rich-renderer structure.
|
|
1702
|
+
*
|
|
1703
|
+
* `structure.css` lays out the grid itself: regions, header, virtualised rows,
|
|
1704
|
+
* the editor host and the edit bar. This layer covers the *inside* of the
|
|
1705
|
+
* editors catalogued in §11.2 and the renderers in §8.8 that draw something
|
|
1706
|
+
* more than text.
|
|
1707
|
+
*
|
|
1708
|
+
* Two rules run through all of it:
|
|
1709
|
+
*
|
|
1710
|
+
* 1. **Every editor must fit a row.** An editor mounts into the cell it is
|
|
1711
|
+
* editing, so anything taller than `--lattice-row-height` pushes the row and
|
|
1712
|
+
* makes the grid jump under the cursor. Editors that genuinely need more
|
|
1713
|
+
* room — the calendar, the code box, the picker lists — are popups and
|
|
1714
|
+
* escape the row instead of stretching it.
|
|
1715
|
+
* 2. **Colour comes from tokens, never from literals.** Everything here reads
|
|
1716
|
+
* the same custom properties as the rest of the theme, so a host that
|
|
1717
|
+
* restyles the grid restyles the editors with it, and dark mode needs no
|
|
1718
|
+
* second stylesheet.
|
|
1719
|
+
*/
|
|
1720
|
+
|
|
1721
|
+
/* ------------------------------------------------------------------ *
|
|
1722
|
+
* Shared editor scaffolding
|
|
1723
|
+
* ------------------------------------------------------------------ */
|
|
1724
|
+
|
|
1725
|
+
/* The wrapper every composite editor puts around its parts. Inline-flex rather
|
|
1726
|
+
* than block so a wrapper inside a cell is still only as tall as its content. */
|
|
1727
|
+
.lattice .lat-editor__unit,
|
|
1728
|
+
.lattice .lat-editor__radix,
|
|
1729
|
+
.lattice .lat-editor__rating,
|
|
1730
|
+
.lattice .lat-editor__slider,
|
|
1731
|
+
.lattice .lat-editor__segmented,
|
|
1732
|
+
.lattice .lat-editor__colour,
|
|
1733
|
+
.lattice .lat-editor__password,
|
|
1734
|
+
.lattice .lat-editor__fields {
|
|
1735
|
+
display: flex;
|
|
1736
|
+
align-items: center;
|
|
1737
|
+
gap: calc(var(--lattice-space) * 0.75);
|
|
1738
|
+
width: 100%;
|
|
1739
|
+
min-width: 0;
|
|
1740
|
+
box-sizing: border-box;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
/*
|
|
1744
|
+
* A popup is absolutely positioned and sizes to its content, so `width: 100%`
|
|
1745
|
+
* there resolves against the grid root rather than the cell and stretches the
|
|
1746
|
+
* editor across the whole viewport. Popups size themselves.
|
|
1747
|
+
*/
|
|
1748
|
+
.lattice .lat-editor--popup,
|
|
1749
|
+
.lattice .lat-editor--popup .lat-editor__unit,
|
|
1750
|
+
.lattice .lat-editor--popup .lat-editor__radix,
|
|
1751
|
+
.lattice .lat-editor--popup .lat-editor__rating,
|
|
1752
|
+
.lattice .lat-editor--popup .lat-editor__slider,
|
|
1753
|
+
.lattice .lat-editor--popup .lat-editor__segmented,
|
|
1754
|
+
.lattice .lat-editor--popup .lat-editor__colour,
|
|
1755
|
+
.lattice .lat-editor--popup .lat-editor__password {
|
|
1756
|
+
width: auto;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/* A composite editor's own input should still fill the space left over. */
|
|
1760
|
+
.lattice .lat-editor__unit input,
|
|
1761
|
+
.lattice .lat-editor__radix input,
|
|
1762
|
+
.lattice .lat-editor__password-field {
|
|
1763
|
+
flex: 1 1 auto;
|
|
1764
|
+
min-width: 0;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
/*
|
|
1768
|
+
* The trailing hint — the unit an entry will be stored in, the decimal value
|
|
1769
|
+
* behind a hex one. It is the smallest thing on screen that stops a whole class
|
|
1770
|
+
* of silent data error, so it is muted but never hidden.
|
|
1771
|
+
*/
|
|
1772
|
+
.lattice .lat-editor__hint,
|
|
1773
|
+
.lattice .lat-editor__suffix {
|
|
1774
|
+
flex: 0 0 auto;
|
|
1775
|
+
font-size: var(--lattice-font-size-sm);
|
|
1776
|
+
color: var(--lattice-foreground-muted);
|
|
1777
|
+
font-variant-numeric: tabular-nums;
|
|
1778
|
+
white-space: nowrap;
|
|
1779
|
+
pointer-events: none;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
.lattice .lat-editor__hint--invalid {
|
|
1783
|
+
color: var(--lattice-variant-danger-text);
|
|
1784
|
+
pointer-events: auto;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
/* ------------------------------------------------------------------ *
|
|
1788
|
+
* Narrow columns
|
|
1789
|
+
* ------------------------------------------------------------------ */
|
|
1790
|
+
|
|
1791
|
+
/*
|
|
1792
|
+
* An inline editor may never spill over its neighbours.
|
|
1793
|
+
*
|
|
1794
|
+
* A composite editor — a slider with a number field, a rating with a readout —
|
|
1795
|
+
* has a natural width larger than a 90px column, and flexbox will happily let
|
|
1796
|
+
* it overflow rather than shrink below its content. Clipping at the cell is the
|
|
1797
|
+
* backstop; the rules under it decide *what* gets dropped, so each editor loses
|
|
1798
|
+
* its least important part rather than having its primary control cut in half.
|
|
1799
|
+
*/
|
|
1800
|
+
.lattice .lat-editor-host > * {
|
|
1801
|
+
max-width: 100%;
|
|
1802
|
+
box-sizing: border-box;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
.lattice .lat-editor__rating,
|
|
1806
|
+
.lattice .lat-editor__slider,
|
|
1807
|
+
.lattice .lat-editor__segmented,
|
|
1808
|
+
.lattice .lat-editor__unit,
|
|
1809
|
+
.lattice .lat-editor__radix {
|
|
1810
|
+
overflow: hidden;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
/* Rating: the stars are the control, the number beside them is a convenience.
|
|
1814
|
+
The readout goes first when there is not enough room for both. */
|
|
1815
|
+
.lattice .lat-editor__rating-value {
|
|
1816
|
+
flex: 0 1 auto;
|
|
1817
|
+
min-width: 0;
|
|
1818
|
+
overflow: hidden;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
.lattice .lat-editor__rating-slider { flex: 0 0 auto; }
|
|
1822
|
+
|
|
1823
|
+
/* Slider: the track is the control. The typed field is the first thing to go,
|
|
1824
|
+
then the readout — a slider you can drag is more use than a number you can
|
|
1825
|
+
read, and the value is visible in the cell the moment the edit commits.
|
|
1826
|
+
|
|
1827
|
+
The selector is host-scoped to beat `.lat-editor-host input { width: 100% }`,
|
|
1828
|
+
which otherwise gives the number field the entire row and pushes the track
|
|
1829
|
+
and the readout out of the cell. */
|
|
1830
|
+
.lattice .lat-editor-host .lat-editor__slider .lat-editor__slider-number,
|
|
1831
|
+
.lattice .lat-editor__slider-number {
|
|
1832
|
+
width: auto;
|
|
1833
|
+
flex: 0 1 4.5em;
|
|
1834
|
+
min-width: 0;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
.lattice .lat-editor-host .lat-editor__slider .lat-editor__slider-input {
|
|
1838
|
+
width: auto;
|
|
1839
|
+
flex: 1 1 4em;
|
|
1840
|
+
min-width: 0;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
.lattice .lat-editor__slider-value {
|
|
1844
|
+
flex: 0 1 auto;
|
|
1845
|
+
min-width: 0;
|
|
1846
|
+
overflow: hidden;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
.lattice .lat-editor__slider-input { flex: 1 1 3em; }
|
|
1850
|
+
|
|
1851
|
+
/* Segmented: the labels ellipsis rather than the group overflowing. Below
|
|
1852
|
+
about four segments in a narrow column the labels become unreadable, which
|
|
1853
|
+
is what `max` and the dropdown fallback are for. */
|
|
1854
|
+
.lattice .lat-editor__segment {
|
|
1855
|
+
min-width: 0;
|
|
1856
|
+
padding: 0 calc(var(--lattice-space) * 0.4);
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
/* ------------------------------------------------------------------ *
|
|
1860
|
+
* Numeric bases and units (§8.5)
|
|
1861
|
+
* ------------------------------------------------------------------ */
|
|
1862
|
+
|
|
1863
|
+
/*
|
|
1864
|
+
* Monospaced, because these are read as digit patterns rather than as words:
|
|
1865
|
+
* `0x00FF` against `0x0FF0` is a comparison the eye makes by column position,
|
|
1866
|
+
* and a proportional font destroys it.
|
|
1867
|
+
*/
|
|
1868
|
+
.lattice .lat-editor__radix input,
|
|
1869
|
+
.lattice .lat-editor__radix .lat-editor__hint {
|
|
1870
|
+
font-family: var(--lattice-font-mono);
|
|
1871
|
+
font-size: var(--lattice-font-size-sm);
|
|
1872
|
+
letter-spacing: 0.02em;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
.lattice .lat-editor__unit input {
|
|
1876
|
+
text-align: right;
|
|
1877
|
+
font-variant-numeric: tabular-nums;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
/* ------------------------------------------------------------------ *
|
|
1881
|
+
* Temporal (§8.2)
|
|
1882
|
+
* ------------------------------------------------------------------ */
|
|
1883
|
+
|
|
1884
|
+
.lattice .lat-editor__datetime {
|
|
1885
|
+
display: block;
|
|
1886
|
+
min-width: 0;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
.lattice .lat-editor__fields > input {
|
|
1890
|
+
min-width: 0;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
/* The date half needs about twice the width of the time half. */
|
|
1894
|
+
.lattice .lat-editor__fields > input:first-child { flex: 3 1 0; }
|
|
1895
|
+
.lattice .lat-editor__fields > input:last-child { flex: 2 1 0; }
|
|
1896
|
+
|
|
1897
|
+
/*
|
|
1898
|
+
* The calendar. Only ever drawn inside a popup — `editor--popup` gives it the
|
|
1899
|
+
* surface, border and shadow — so it carries spacing and grid only.
|
|
1900
|
+
*/
|
|
1901
|
+
.lattice .lat-editor__calendar {
|
|
1902
|
+
padding: calc(var(--lattice-space) * 0.75);
|
|
1903
|
+
user-select: none;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
.lattice .lat-editor__month {
|
|
1907
|
+
display: flex;
|
|
1908
|
+
align-items: center;
|
|
1909
|
+
justify-content: space-between;
|
|
1910
|
+
gap: var(--lattice-space);
|
|
1911
|
+
padding-bottom: calc(var(--lattice-space) * 0.5);
|
|
1912
|
+
font-size: var(--lattice-font-size-sm);
|
|
1913
|
+
font-weight: 600;
|
|
1914
|
+
font-variant-numeric: tabular-nums;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
.lattice .lat-editor__month button {
|
|
1918
|
+
border: 0;
|
|
1919
|
+
background: none;
|
|
1920
|
+
color: inherit;
|
|
1921
|
+
cursor: pointer;
|
|
1922
|
+
border-radius: var(--lattice-radius-sm);
|
|
1923
|
+
padding: 0 calc(var(--lattice-space) * 0.5);
|
|
1924
|
+
line-height: 1.6;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
.lattice .lat-editor__month button:hover { background: var(--lattice-hovered-background); }
|
|
1928
|
+
|
|
1929
|
+
.lattice .lat-editor__days {
|
|
1930
|
+
display: grid;
|
|
1931
|
+
grid-template-columns: repeat(7, 2em);
|
|
1932
|
+
gap: 2px;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
.lattice .lat-editor__day,
|
|
1936
|
+
.lattice .lat-editor__day--empty {
|
|
1937
|
+
display: flex;
|
|
1938
|
+
align-items: center;
|
|
1939
|
+
justify-content: center;
|
|
1940
|
+
height: 2em;
|
|
1941
|
+
font-size: var(--lattice-font-size-sm);
|
|
1942
|
+
font-variant-numeric: tabular-nums;
|
|
1943
|
+
border: 0;
|
|
1944
|
+
border-radius: var(--lattice-radius-sm);
|
|
1945
|
+
background: none;
|
|
1946
|
+
color: inherit;
|
|
1947
|
+
cursor: pointer;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
.lattice .lat-editor__day--empty {
|
|
1951
|
+
cursor: default;
|
|
1952
|
+
pointer-events: none;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
.lattice .lat-editor__day:hover { background: var(--lattice-hovered-background); }
|
|
1956
|
+
|
|
1957
|
+
.lattice .lat-editor__day[aria-selected='true'] {
|
|
1958
|
+
background: var(--lattice-accent);
|
|
1959
|
+
color: var(--lattice-accent-contrast);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
/* Today reads as an outline, so it never competes with the selection. */
|
|
1963
|
+
.lattice .lat-editor__day[data-today='true']:not([aria-selected='true']) {
|
|
1964
|
+
box-shadow: inset 0 0 0 1px var(--lattice-accent);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
/* ------------------------------------------------------------------ *
|
|
1968
|
+
* Secrets (§11.2)
|
|
1969
|
+
* ------------------------------------------------------------------ */
|
|
1970
|
+
|
|
1971
|
+
/*
|
|
1972
|
+
* The single-field case must stay on one line.
|
|
1973
|
+
*
|
|
1974
|
+
* `flex-wrap: wrap` plus an input the editor host stretches to `width: 100%`
|
|
1975
|
+
* left no room for the reveal toggle, so it wrapped onto a second row and the
|
|
1976
|
+
* editor stood 39px tall inside a 27px cell — the button hanging below the
|
|
1977
|
+
* bottom border. Wrapping is only wanted when there genuinely is a second line:
|
|
1978
|
+
* a confirm field or a hint.
|
|
1979
|
+
*/
|
|
1980
|
+
.lattice .lat-editor__password { flex-wrap: nowrap; }
|
|
1981
|
+
|
|
1982
|
+
.lattice .lat-editor__password:has(.lat-editor__password-confirm),
|
|
1983
|
+
.lattice .lat-editor__password:has(.lat-editor__password-hint) {
|
|
1984
|
+
flex-wrap: wrap;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
/* Beat the editor host's blanket `width: 100%`: inside a flex row the field
|
|
1988
|
+
shares the space with the toggle rather than claiming all of it. */
|
|
1989
|
+
.lattice .lat-editor-host .lat-editor__password .lat-editor__password-field {
|
|
1990
|
+
width: auto;
|
|
1991
|
+
flex: 1 1 auto;
|
|
1992
|
+
min-width: 0;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
.lattice .lat-editor__password-confirm { flex: 1 1 100%; }
|
|
1996
|
+
|
|
1997
|
+
.lattice .lat-editor__password-field {
|
|
1998
|
+
font-family: var(--lattice-font-mono);
|
|
1999
|
+
letter-spacing: 0.08em;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
/*
|
|
2003
|
+
* The mask itself.
|
|
2004
|
+
*
|
|
2005
|
+
* A `type="text"` input masked in CSS rather than a real password field,
|
|
2006
|
+
* because Chrome offers to save any password field's contents to the user's
|
|
2007
|
+
* password manager and ignores `autocomplete="off"` when deciding to. A device
|
|
2008
|
+
* secret in a grid cell is the customer's data, not the user's credential, and
|
|
2009
|
+
* it does not belong in their personal vault. The editor falls back to a real
|
|
2010
|
+
* password field where this property is unsupported — an unmasked secret is
|
|
2011
|
+
* worse than an unwanted prompt.
|
|
2012
|
+
*/
|
|
2013
|
+
.lattice .lat-editor__password-field[data-masked='true'] {
|
|
2014
|
+
-webkit-text-security: disc;
|
|
2015
|
+
text-security: disc;
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
/* Reveal is a toggle, so it holds a pressed state rather than swapping shape. */
|
|
2019
|
+
.lattice .lat-editor__reveal {
|
|
2020
|
+
flex: 0 0 auto;
|
|
2021
|
+
display: inline-flex;
|
|
2022
|
+
align-items: center;
|
|
2023
|
+
justify-content: center;
|
|
2024
|
+
width: calc(var(--lattice-icon-size) + var(--lattice-space));
|
|
2025
|
+
height: calc(var(--lattice-icon-size) + var(--lattice-space));
|
|
2026
|
+
padding: 0;
|
|
2027
|
+
border: 0;
|
|
2028
|
+
border-radius: var(--lattice-radius-sm);
|
|
2029
|
+
background: none;
|
|
2030
|
+
color: var(--lattice-foreground-muted);
|
|
2031
|
+
cursor: pointer;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
.lattice .lat-editor__reveal:hover { background: var(--lattice-hovered-background); }
|
|
2035
|
+
.lattice .lat-editor__reveal--on { color: var(--lattice-accent); }
|
|
2036
|
+
|
|
2037
|
+
.lattice .lat-editor__password-hint {
|
|
2038
|
+
flex: 1 1 100%;
|
|
2039
|
+
font-size: var(--lattice-font-size-sm);
|
|
2040
|
+
color: var(--lattice-foreground-muted);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
/* ------------------------------------------------------------------ *
|
|
2044
|
+
* Code and JSON (§11.2)
|
|
2045
|
+
* ------------------------------------------------------------------ */
|
|
2046
|
+
|
|
2047
|
+
.lattice .lat-editor__code {
|
|
2048
|
+
display: flex;
|
|
2049
|
+
flex-direction: column;
|
|
2050
|
+
min-width: 32ch;
|
|
2051
|
+
max-width: min(72ch, 90vw);
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
.lattice .lat-editor__code--resizable { resize: both; overflow: auto; }
|
|
2055
|
+
|
|
2056
|
+
.lattice .lat-editor__code-toolbar {
|
|
2057
|
+
display: flex;
|
|
2058
|
+
align-items: center;
|
|
2059
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
2060
|
+
padding: calc(var(--lattice-space) * 0.5);
|
|
2061
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
2062
|
+
background: var(--lattice-surface-alt);
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
.lattice .lat-editor__code-language {
|
|
2066
|
+
font-size: var(--lattice-font-size-sm);
|
|
2067
|
+
font-weight: 600;
|
|
2068
|
+
letter-spacing: 0.06em;
|
|
2069
|
+
color: var(--lattice-foreground-muted);
|
|
2070
|
+
margin-right: auto;
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
.lattice .lat-editor__code-action {
|
|
2074
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
2075
|
+
border-radius: var(--lattice-radius-sm);
|
|
2076
|
+
background: var(--lattice-surface);
|
|
2077
|
+
color: inherit;
|
|
2078
|
+
font-size: var(--lattice-font-size-sm);
|
|
2079
|
+
padding: 2px calc(var(--lattice-space) * 0.75);
|
|
2080
|
+
cursor: pointer;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
.lattice .lat-editor__code-action:hover { background: var(--lattice-hovered-background); }
|
|
2084
|
+
|
|
2085
|
+
.lattice .lat-editor__code-area {
|
|
2086
|
+
flex: 1 1 auto;
|
|
2087
|
+
min-height: 8em;
|
|
2088
|
+
border: 0;
|
|
2089
|
+
padding: calc(var(--lattice-space) * 0.75);
|
|
2090
|
+
font-family: var(--lattice-font-mono);
|
|
2091
|
+
font-size: var(--lattice-font-size-sm);
|
|
2092
|
+
line-height: 1.5;
|
|
2093
|
+
/* Tabs indent by two columns, matching what the Tab key inserts. */
|
|
2094
|
+
tab-size: 2;
|
|
2095
|
+
white-space: pre;
|
|
2096
|
+
overflow: auto;
|
|
2097
|
+
resize: none;
|
|
2098
|
+
background: var(--lattice-surface);
|
|
2099
|
+
color: var(--lattice-foreground);
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
.lattice .lat-editor__code-area:focus { outline: none; }
|
|
2103
|
+
|
|
2104
|
+
/*
|
|
2105
|
+
* The error line holds its space whether or not it is showing. Collapsing it
|
|
2106
|
+
* would reflow the whole popup on every keystroke that fixes or breaks the
|
|
2107
|
+
* syntax, which is exactly when the user is reading it.
|
|
2108
|
+
*/
|
|
2109
|
+
.lattice .lat-editor__code-error {
|
|
2110
|
+
min-height: 1.5em;
|
|
2111
|
+
padding: 0 calc(var(--lattice-space) * 0.75);
|
|
2112
|
+
font-size: var(--lattice-font-size-sm);
|
|
2113
|
+
font-family: var(--lattice-font-mono);
|
|
2114
|
+
color: transparent;
|
|
2115
|
+
border-top: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
2116
|
+
transition: color var(--lattice-transition);
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
.lattice .lat-editor__code-error--visible {
|
|
2120
|
+
color: var(--lattice-variant-danger-text);
|
|
2121
|
+
background: var(--lattice-variant-danger-fill);
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
.lattice .lat-editor__code-hint {
|
|
2125
|
+
padding: calc(var(--lattice-space) * 0.5) calc(var(--lattice-space) * 0.75);
|
|
2126
|
+
font-size: var(--lattice-font-size-sm);
|
|
2127
|
+
color: var(--lattice-foreground-muted);
|
|
2128
|
+
border-top: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
/* The fault marker the validator points at, inside the error text. */
|
|
2132
|
+
.lattice .lat-code-err {
|
|
2133
|
+
text-decoration: underline wavy var(--lattice-variant-danger-text);
|
|
2134
|
+
text-underline-offset: 2px;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
/* ------------------------------------------------------------------ *
|
|
2138
|
+
* Segmented choice (§11.2)
|
|
2139
|
+
* ------------------------------------------------------------------ */
|
|
2140
|
+
|
|
2141
|
+
/*
|
|
2142
|
+
* One visible button per option, for the case a dropdown handles badly: three
|
|
2143
|
+
* or four short values where the click to open the list costs more than the
|
|
2144
|
+
* space the options take.
|
|
2145
|
+
*/
|
|
2146
|
+
.lattice .lat-editor__segmented {
|
|
2147
|
+
gap: 0;
|
|
2148
|
+
height: 100%;
|
|
2149
|
+
box-sizing: border-box;
|
|
2150
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
2151
|
+
border-radius: var(--lattice-radius);
|
|
2152
|
+
background: var(--lattice-surface-alt);
|
|
2153
|
+
padding: 1px;
|
|
2154
|
+
overflow: hidden;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
.lattice .lat-editor__segment {
|
|
2158
|
+
flex: 1 1 0;
|
|
2159
|
+
display: inline-flex;
|
|
2160
|
+
align-items: center;
|
|
2161
|
+
justify-content: center;
|
|
2162
|
+
gap: calc(var(--lattice-space) * 0.4);
|
|
2163
|
+
min-width: 0;
|
|
2164
|
+
padding: 0 calc(var(--lattice-space) * 0.6);
|
|
2165
|
+
/* Follow the row the grid actually has, not the theme's default row token —
|
|
2166
|
+
a grid configured with `rowHeight: 24` still carries the 28px token, and
|
|
2167
|
+
sizing from it made the control taller than the cell holding it. */
|
|
2168
|
+
height: 100%;
|
|
2169
|
+
border: 0;
|
|
2170
|
+
border-radius: calc(var(--lattice-radius) - 1px);
|
|
2171
|
+
background: none;
|
|
2172
|
+
color: var(--lattice-foreground-muted);
|
|
2173
|
+
font: inherit;
|
|
2174
|
+
font-size: var(--lattice-font-size-sm);
|
|
2175
|
+
line-height: 1;
|
|
2176
|
+
cursor: pointer;
|
|
2177
|
+
white-space: nowrap;
|
|
2178
|
+
transition: background var(--lattice-transition), color var(--lattice-transition);
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
.lattice .lat-editor__segment-label {
|
|
2182
|
+
overflow: hidden;
|
|
2183
|
+
text-overflow: ellipsis;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
.lattice .lat-editor__segment:hover:not(.lat-editor__segment--disabled) {
|
|
2187
|
+
background: var(--lattice-hovered-background);
|
|
2188
|
+
color: var(--lattice-foreground);
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
/*
|
|
2192
|
+
* The active segment is filled, not merely tinted. §11.2 asks for the selected
|
|
2193
|
+
* option to be unmistakable, and a segmented control with a low-contrast
|
|
2194
|
+
* selection is read as "nothing is selected".
|
|
2195
|
+
*/
|
|
2196
|
+
.lattice .lat-editor__segment--active,
|
|
2197
|
+
.lattice .lat-editor__segment[aria-checked='true'] {
|
|
2198
|
+
background: var(--lattice-accent);
|
|
2199
|
+
color: var(--lattice-accent-contrast);
|
|
2200
|
+
font-weight: 600;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
.lattice .lat-editor__segment--disabled {
|
|
2204
|
+
opacity: 0.45;
|
|
2205
|
+
cursor: not-allowed;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
/* ------------------------------------------------------------------ *
|
|
2209
|
+
* Rating (§8.8, §11.2)
|
|
2210
|
+
* ------------------------------------------------------------------ */
|
|
2211
|
+
|
|
2212
|
+
.lattice .lat-editor__rating,
|
|
2213
|
+
.lattice .lat-rating {
|
|
2214
|
+
display: inline-flex;
|
|
2215
|
+
align-items: center;
|
|
2216
|
+
gap: calc(var(--lattice-space) * 0.25);
|
|
2217
|
+
line-height: 1;
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
.lattice .lat-rating__items {
|
|
2221
|
+
display: inline-flex;
|
|
2222
|
+
align-items: center;
|
|
2223
|
+
gap: calc(var(--lattice-space) * 0.25);
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
.lattice .lat-editor__rating-item,
|
|
2227
|
+
.lattice .lat-rating__item {
|
|
2228
|
+
position: relative;
|
|
2229
|
+
display: inline-flex;
|
|
2230
|
+
flex: 0 0 auto;
|
|
2231
|
+
width: var(--lattice-icon-size);
|
|
2232
|
+
height: var(--lattice-icon-size);
|
|
2233
|
+
color: var(--lattice-border-strong);
|
|
2234
|
+
border: 0;
|
|
2235
|
+
padding: 0;
|
|
2236
|
+
background: none;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
.lattice .lat-editor__rating-item { cursor: pointer; }
|
|
2240
|
+
|
|
2241
|
+
/*
|
|
2242
|
+
* Two stacked copies of the glyph: the base is the empty colour, and the
|
|
2243
|
+
* overlay is the filled one, clipped from the left to the fill fraction. A
|
|
2244
|
+
* half rating therefore shows the left of the star filled and the right of it
|
|
2245
|
+
* still grey — which one clipped glyph could not do, because clipping would
|
|
2246
|
+
* erase the empty half instead of leaving it visible.
|
|
2247
|
+
*/
|
|
2248
|
+
.lattice .lat-rating__fill,
|
|
2249
|
+
.lattice .lat-editor__rating-fill {
|
|
2250
|
+
position: absolute;
|
|
2251
|
+
inset: 0;
|
|
2252
|
+
width: var(--lat-rating-fill, 0%);
|
|
2253
|
+
overflow: hidden;
|
|
2254
|
+
color: var(--lattice-variant-warning-solid);
|
|
2255
|
+
pointer-events: none;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
/* The clipped copy must keep its full width, or it would shrink to fit the
|
|
2259
|
+
* clip and scale the glyph instead of cutting it. */
|
|
2260
|
+
.lattice .lat-rating__fill .lat-icon,
|
|
2261
|
+
.lattice .lat-editor__rating-fill .lat-icon {
|
|
2262
|
+
width: var(--lattice-icon-size);
|
|
2263
|
+
height: var(--lattice-icon-size);
|
|
2264
|
+
max-width: none;
|
|
2265
|
+
flex: 0 0 auto;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
.lattice .lat-editor__rating-value,
|
|
2269
|
+
.lattice .lat-rating__value {
|
|
2270
|
+
font-size: var(--lattice-font-size-sm);
|
|
2271
|
+
color: var(--lattice-foreground-muted);
|
|
2272
|
+
font-variant-numeric: tabular-nums;
|
|
2273
|
+
margin-left: calc(var(--lattice-space) * 0.4);
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
/*
|
|
2277
|
+
* `role="slider"` on the icon row itself, not a native input behind it: the
|
|
2278
|
+
* icons *are* the control, so they carry the focus ring and the arrow keys,
|
|
2279
|
+
* and there is no second focusable element for the theme to hide.
|
|
2280
|
+
*/
|
|
2281
|
+
.lattice .lat-editor__rating-slider {
|
|
2282
|
+
display: inline-flex;
|
|
2283
|
+
align-items: center;
|
|
2284
|
+
gap: calc(var(--lattice-space) * 0.25);
|
|
2285
|
+
border-radius: var(--lattice-radius-sm);
|
|
2286
|
+
cursor: pointer;
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
.lattice .lat-editor__rating-slider:focus-visible {
|
|
2290
|
+
outline: var(--lattice-focus-width) solid var(--lattice-focus-color);
|
|
2291
|
+
outline-offset: var(--lattice-focus-offset);
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
/* ------------------------------------------------------------------ *
|
|
2295
|
+
* Slider (§11.2)
|
|
2296
|
+
* ------------------------------------------------------------------ */
|
|
2297
|
+
|
|
2298
|
+
.lattice .lat-editor__slider { gap: calc(var(--lattice-space) * 0.75); }
|
|
2299
|
+
|
|
2300
|
+
.lattice .lat-editor__slider-input {
|
|
2301
|
+
flex: 1 1 auto;
|
|
2302
|
+
min-width: 4em;
|
|
2303
|
+
accent-color: var(--lattice-accent);
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
/* The typed alternative to dragging: a slider alone cannot express "exactly 40". */
|
|
2307
|
+
.lattice .lat-editor__slider-number {
|
|
2308
|
+
flex: 0 0 auto;
|
|
2309
|
+
width: 5em;
|
|
2310
|
+
min-width: 0;
|
|
2311
|
+
text-align: right;
|
|
2312
|
+
font-variant-numeric: tabular-nums;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
.lattice .lat-editor__slider-value {
|
|
2316
|
+
flex: 0 0 auto;
|
|
2317
|
+
min-width: 3ch;
|
|
2318
|
+
text-align: right;
|
|
2319
|
+
font-size: var(--lattice-font-size-sm);
|
|
2320
|
+
font-variant-numeric: tabular-nums;
|
|
2321
|
+
color: var(--lattice-foreground-muted);
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
.lattice .lat-editor__slider-marks {
|
|
2325
|
+
display: flex;
|
|
2326
|
+
justify-content: space-between;
|
|
2327
|
+
flex: 1 1 100%;
|
|
2328
|
+
order: 10;
|
|
2329
|
+
font-size: var(--lattice-font-size-sm);
|
|
2330
|
+
color: var(--lattice-foreground-muted);
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
.lattice .lat-editor__slider-mark { white-space: nowrap; }
|
|
2334
|
+
|
|
2335
|
+
/* ------------------------------------------------------------------ *
|
|
2336
|
+
* Colour (§8.8, §11.2)
|
|
2337
|
+
* ------------------------------------------------------------------ */
|
|
2338
|
+
|
|
2339
|
+
.lattice .lat-editor__colour-preview,
|
|
2340
|
+
.lattice .lat-colour__swatch {
|
|
2341
|
+
flex: 0 0 auto;
|
|
2342
|
+
display: inline-block;
|
|
2343
|
+
width: var(--lattice-icon-size);
|
|
2344
|
+
height: var(--lattice-icon-size);
|
|
2345
|
+
border-radius: var(--lattice-radius-sm);
|
|
2346
|
+
/* An outline, so white on white is still a visible swatch. */
|
|
2347
|
+
box-shadow: inset 0 0 0 1px var(--lattice-border-strong);
|
|
2348
|
+
background: var(--lat-swatch, transparent);
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
/* No colour at all reads as a hatch, so "unset" is distinguishable from white. */
|
|
2352
|
+
.lattice .lat-colour__swatch[data-empty='true'],
|
|
2353
|
+
.lattice .lat-editor__colour-preview[data-empty='true'] {
|
|
2354
|
+
background:
|
|
2355
|
+
linear-gradient(135deg, transparent 45%, var(--lattice-border-strong) 45%,
|
|
2356
|
+
var(--lattice-border-strong) 55%, transparent 55%);
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
/* Inside the popup the parts stack: preview and hex on one line, the swatch
|
|
2360
|
+
* grid beneath. Wrapping rather than a fixed column count so a longer palette
|
|
2361
|
+
* grows downwards instead of pushing the popup off the viewport. */
|
|
2362
|
+
.lattice .lat-editor__colour {
|
|
2363
|
+
flex-wrap: wrap;
|
|
2364
|
+
padding: calc(var(--lattice-space) * 0.75);
|
|
2365
|
+
min-width: 13em;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
.lattice .lat-editor__colour-swatches {
|
|
2369
|
+
display: grid;
|
|
2370
|
+
grid-template-columns: repeat(6, calc(var(--lattice-icon-size) * 1.25));
|
|
2371
|
+
gap: calc(var(--lattice-space) * 0.4);
|
|
2372
|
+
order: 10;
|
|
2373
|
+
flex: 1 1 100%;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
.lattice .lat-editor__colour-swatch {
|
|
2377
|
+
width: calc(var(--lattice-icon-size) * 1.25);
|
|
2378
|
+
height: calc(var(--lattice-icon-size) * 1.25);
|
|
2379
|
+
padding: 0;
|
|
2380
|
+
border: 0;
|
|
2381
|
+
border-radius: var(--lattice-radius-sm);
|
|
2382
|
+
box-shadow: inset 0 0 0 1px var(--lattice-border-strong);
|
|
2383
|
+
background: var(--lat-swatch, transparent);
|
|
2384
|
+
cursor: pointer;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
/*
|
|
2388
|
+
* A token swatch is painted by the theme, not by a value the editor computed:
|
|
2389
|
+
* `data-variant="success"` resolves to the same `--lattice-variant-*-solid` the
|
|
2390
|
+
* pills and decorations use, so retuning the theme retunes the palette the
|
|
2391
|
+
* editor offers. Only a free-form hex needs the custom property, because an
|
|
2392
|
+
* arbitrary colour out of host configuration cannot be a pre-generated class.
|
|
2393
|
+
*/
|
|
2394
|
+
.lattice .lat-editor__colour-swatch[data-variant='neutral'] { background: var(--lattice-variant-neutral-solid); }
|
|
2395
|
+
.lattice .lat-editor__colour-swatch[data-variant='info'] { background: var(--lattice-variant-info-solid); }
|
|
2396
|
+
.lattice .lat-editor__colour-swatch[data-variant='success'] { background: var(--lattice-variant-success-solid); }
|
|
2397
|
+
.lattice .lat-editor__colour-swatch[data-variant='warning'] { background: var(--lattice-variant-warning-solid); }
|
|
2398
|
+
.lattice .lat-editor__colour-swatch[data-variant='danger'] { background: var(--lattice-variant-danger-solid); }
|
|
2399
|
+
.lattice .lat-editor__colour-swatch[data-variant='accent'] { background: var(--lattice-variant-accent-solid); }
|
|
2400
|
+
.lattice .lat-editor__colour-swatch[data-variant='none'] { background: var(--lattice-variant-none-solid); }
|
|
2401
|
+
|
|
2402
|
+
.lattice .lat-editor__colour-swatch--selected,
|
|
2403
|
+
.lattice .lat-editor__colour-swatch[aria-checked='true'] {
|
|
2404
|
+
box-shadow:
|
|
2405
|
+
inset 0 0 0 1px var(--lattice-border-strong),
|
|
2406
|
+
0 0 0 2px var(--lattice-accent);
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
.lattice .lat-editor__colour-hex {
|
|
2410
|
+
flex: 1 1 auto;
|
|
2411
|
+
min-width: 8ch;
|
|
2412
|
+
font-family: var(--lattice-font-mono);
|
|
2413
|
+
text-transform: lowercase;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
.lattice .lat-editor__colour-native {
|
|
2417
|
+
flex: 0 0 auto;
|
|
2418
|
+
width: calc(var(--lattice-icon-size) + var(--lattice-space));
|
|
2419
|
+
height: calc(var(--lattice-icon-size) + var(--lattice-space));
|
|
2420
|
+
padding: 0;
|
|
2421
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
2422
|
+
border-radius: var(--lattice-radius-sm);
|
|
2423
|
+
background: none;
|
|
2424
|
+
cursor: pointer;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
/* The read-only presentation: swatch then the value as text. */
|
|
2428
|
+
.lattice .lat-colour {
|
|
2429
|
+
display: inline-flex;
|
|
2430
|
+
align-items: center;
|
|
2431
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
2432
|
+
min-width: 0;
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
.lattice .lat-colour__text {
|
|
2436
|
+
font-family: var(--lattice-font-mono);
|
|
2437
|
+
font-size: var(--lattice-font-size-sm);
|
|
2438
|
+
overflow: hidden;
|
|
2439
|
+
text-overflow: ellipsis;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
/* ------------------------------------------------------------------ *
|
|
2443
|
+
* Tree select (§11.2)
|
|
2444
|
+
* ------------------------------------------------------------------ */
|
|
2445
|
+
|
|
2446
|
+
.lattice .lat-treeselect .lat-list__option {
|
|
2447
|
+
/* Indent is drawn by the guide elements, not by padding, so the twisty of a
|
|
2448
|
+
* deep node stays clickable at a predictable offset. */
|
|
2449
|
+
padding-left: calc(var(--lattice-space) * 0.5);
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
.lattice .lat-tree__indent {
|
|
2453
|
+
flex: 0 0 auto;
|
|
2454
|
+
display: inline-flex;
|
|
2455
|
+
align-self: stretch;
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
.lattice .lat-tree__guide {
|
|
2459
|
+
width: var(--lattice-indent-size);
|
|
2460
|
+
align-self: stretch;
|
|
2461
|
+
/* A hairline down the middle of each ancestor's indent, so the eye can follow
|
|
2462
|
+
* a child back to its parent across a scrolled list. */
|
|
2463
|
+
background:
|
|
2464
|
+
linear-gradient(var(--lattice-border-color), var(--lattice-border-color))
|
|
2465
|
+
center / 1px 100% no-repeat;
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
.lattice .lat-tree__twisty {
|
|
2469
|
+
flex: 0 0 auto;
|
|
2470
|
+
display: inline-flex;
|
|
2471
|
+
align-items: center;
|
|
2472
|
+
justify-content: center;
|
|
2473
|
+
width: var(--lattice-icon-size);
|
|
2474
|
+
height: var(--lattice-icon-size);
|
|
2475
|
+
border: 0;
|
|
2476
|
+
padding: 0;
|
|
2477
|
+
background: none;
|
|
2478
|
+
color: var(--lattice-foreground-muted);
|
|
2479
|
+
cursor: pointer;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
/* A leaf keeps the twisty's width so its label lines up with its siblings'. */
|
|
2483
|
+
.lattice .lat-tree__twisty--leaf {
|
|
2484
|
+
visibility: hidden;
|
|
2485
|
+
cursor: default;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
.lattice .lat-tree__row--branch { font-weight: 600; }
|
|
2489
|
+
|
|
2490
|
+
/* The ancestor path, shown on the closed control so a selected leaf is not
|
|
2491
|
+
* ambiguous — "Sales" under two different regions is two different options. */
|
|
2492
|
+
.lattice .lat-tree__path {
|
|
2493
|
+
font-size: var(--lattice-font-size-sm);
|
|
2494
|
+
color: var(--lattice-foreground-muted);
|
|
2495
|
+
margin-left: calc(var(--lattice-space) * 0.5);
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
/* ------------------------------------------------------------------ *
|
|
2499
|
+
* Object picker (§11.2)
|
|
2500
|
+
* ------------------------------------------------------------------ */
|
|
2501
|
+
|
|
2502
|
+
.lattice .lat-object__row {
|
|
2503
|
+
display: flex;
|
|
2504
|
+
align-items: center;
|
|
2505
|
+
gap: calc(var(--lattice-space) * 0.75);
|
|
2506
|
+
min-width: 0;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
.lattice .lat-object__text {
|
|
2510
|
+
display: flex;
|
|
2511
|
+
flex-direction: column;
|
|
2512
|
+
min-width: 0;
|
|
2513
|
+
line-height: 1.25;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
.lattice .lat-object__primary {
|
|
2517
|
+
overflow: hidden;
|
|
2518
|
+
text-overflow: ellipsis;
|
|
2519
|
+
white-space: nowrap;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
.lattice .lat-object__secondary {
|
|
2523
|
+
font-size: var(--lattice-font-size-sm);
|
|
2524
|
+
color: var(--lattice-foreground-muted);
|
|
2525
|
+
overflow: hidden;
|
|
2526
|
+
text-overflow: ellipsis;
|
|
2527
|
+
white-space: nowrap;
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
.lattice .lat-avatar {
|
|
2531
|
+
flex: 0 0 auto;
|
|
2532
|
+
position: relative;
|
|
2533
|
+
display: inline-flex;
|
|
2534
|
+
align-items: center;
|
|
2535
|
+
justify-content: center;
|
|
2536
|
+
width: calc(var(--lattice-icon-size) * 1.5);
|
|
2537
|
+
height: calc(var(--lattice-icon-size) * 1.5);
|
|
2538
|
+
border-radius: var(--lattice-radius-pill);
|
|
2539
|
+
overflow: hidden;
|
|
2540
|
+
background: var(--lattice-variant-neutral-fill);
|
|
2541
|
+
color: var(--lattice-variant-neutral-text);
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
.lattice .lat-avatar__image {
|
|
2545
|
+
width: 100%;
|
|
2546
|
+
height: 100%;
|
|
2547
|
+
object-fit: cover;
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
/* Initials are the fallback, so they must be legible at avatar size rather than
|
|
2551
|
+
* merely present: a small, heavy, letter-spaced pair reads better than the
|
|
2552
|
+
* scaled-down body font. */
|
|
2553
|
+
.lattice .lat-avatar__initials {
|
|
2554
|
+
font-size: var(--lattice-font-size-sm);
|
|
2555
|
+
font-weight: 700;
|
|
2556
|
+
letter-spacing: 0.02em;
|
|
2557
|
+
text-transform: uppercase;
|
|
2558
|
+
user-select: none;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
/* ------------------------------------------------------------------ *
|
|
2562
|
+
* Icon picker (§11.2)
|
|
2563
|
+
* ------------------------------------------------------------------ */
|
|
2564
|
+
|
|
2565
|
+
.lattice .lat-iconpicker { min-width: 14em; }
|
|
2566
|
+
|
|
2567
|
+
.lattice .lat-iconpicker__row {
|
|
2568
|
+
display: flex;
|
|
2569
|
+
align-items: center;
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
.lattice .lat-iconpicker__cell {
|
|
2573
|
+
display: inline-flex;
|
|
2574
|
+
align-items: center;
|
|
2575
|
+
justify-content: center;
|
|
2576
|
+
width: calc(var(--lattice-icon-size) * 2);
|
|
2577
|
+
height: calc(var(--lattice-icon-size) * 2);
|
|
2578
|
+
border: 0;
|
|
2579
|
+
border-radius: var(--lattice-radius-sm);
|
|
2580
|
+
background: none;
|
|
2581
|
+
color: var(--lattice-foreground);
|
|
2582
|
+
cursor: pointer;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
.lattice .lat-iconpicker__cell--empty {
|
|
2586
|
+
cursor: default;
|
|
2587
|
+
pointer-events: none;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
.lattice .lat-iconpicker__cell--active {
|
|
2591
|
+
background: var(--lattice-hovered-background);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
.lattice .lat-iconpicker__cell--selected {
|
|
2595
|
+
background: var(--lattice-accent);
|
|
2596
|
+
color: var(--lattice-accent-contrast);
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
.lattice .lat-iconpicker__clear,
|
|
2600
|
+
.lattice .lat-iconpicker__none {
|
|
2601
|
+
display: block;
|
|
2602
|
+
width: 100%;
|
|
2603
|
+
padding: calc(var(--lattice-space) * 0.5);
|
|
2604
|
+
border: 0;
|
|
2605
|
+
border-top: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
2606
|
+
background: none;
|
|
2607
|
+
color: var(--lattice-foreground-muted);
|
|
2608
|
+
font: inherit;
|
|
2609
|
+
font-size: var(--lattice-font-size-sm);
|
|
2610
|
+
text-align: left;
|
|
2611
|
+
cursor: pointer;
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
.lattice .lat-iconpicker__none { cursor: default; }
|
|
2615
|
+
.lattice .lat-iconpicker__clear:hover { background: var(--lattice-hovered-background); }
|
|
2616
|
+
|
|
2617
|
+
/* ------------------------------------------------------------------ *
|
|
2618
|
+
* QR code (§8.8)
|
|
2619
|
+
* ------------------------------------------------------------------ */
|
|
2620
|
+
|
|
2621
|
+
/*
|
|
2622
|
+
* A QR symbol must be dark-on-light to scan, whatever the theme is doing. The
|
|
2623
|
+
* renderer paints the modules with `currentcolor` and draws no background, so
|
|
2624
|
+
* both are set here — and deliberately *not* inverted in dark mode. An inverted
|
|
2625
|
+
* code is not a styling preference; most readers will not decode it.
|
|
2626
|
+
*/
|
|
2627
|
+
.lattice .lat-qrcode {
|
|
2628
|
+
display: inline-flex;
|
|
2629
|
+
align-items: center;
|
|
2630
|
+
justify-content: center;
|
|
2631
|
+
height: 100%;
|
|
2632
|
+
padding: 2px;
|
|
2633
|
+
border-radius: var(--lattice-radius-sm);
|
|
2634
|
+
background: #fff;
|
|
2635
|
+
color: #000;
|
|
2636
|
+
/* The quiet zone is part of the symbol; without it the finder patterns at the
|
|
2637
|
+
* edge are unreliable. */
|
|
2638
|
+
box-shadow: 0 0 0 2px #fff;
|
|
2639
|
+
max-width: 100%;
|
|
2640
|
+
max-height: 100%;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
.lattice .lat-qrcode__svg {
|
|
2644
|
+
display: block;
|
|
2645
|
+
/* Square and clamped to whichever of the row or the column is smaller. The
|
|
2646
|
+
* `size` prop sets the symbol's ideal edge; this stops it overflowing when
|
|
2647
|
+
* the row cannot give it that much. A symbol below roughly 21 device pixels
|
|
2648
|
+
* per side stops being scannable — a QR column wants a taller row, which is
|
|
2649
|
+
* what `rowHeight` is for. */
|
|
2650
|
+
width: auto;
|
|
2651
|
+
height: 100%;
|
|
2652
|
+
max-width: 100%;
|
|
2653
|
+
/* Nearest-neighbour: a resampled module edge is what breaks a marginal scan. */
|
|
2654
|
+
image-rendering: pixelated;
|
|
2655
|
+
shape-rendering: crispEdges;
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
.lattice .lat-qrcode[data-state='empty'],
|
|
2659
|
+
.lattice .lat-qrcode[data-state='overflow'] {
|
|
2660
|
+
background: none;
|
|
2661
|
+
box-shadow: none;
|
|
2662
|
+
color: var(--lattice-foreground-muted);
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
.lattice .lat-qrcode[data-state='ok'] .lat-qrcode__placeholder,
|
|
2666
|
+
.lattice .lat-qrcode:not([data-state='ok']) .lat-qrcode__svg {
|
|
2667
|
+
display: none;
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
.lattice .lat-qrcode__placeholder {
|
|
2671
|
+
font-size: var(--lattice-font-size-sm);
|
|
2672
|
+
color: var(--lattice-foreground-muted);
|
|
2673
|
+
white-space: nowrap;
|
|
2674
|
+
overflow: hidden;
|
|
2675
|
+
text-overflow: ellipsis;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
|
|
2679
|
+
/*
|
|
2680
|
+
* The undo toolbar (§11.1).
|
|
2681
|
+
*
|
|
2682
|
+
* A pair of buttons and the timeline behind them. Sized to sit in a host's own
|
|
2683
|
+
* toolbar rather than to dominate it: the grid does not own the page, and an
|
|
2684
|
+
* undo control that looks like a call to action is wrong for something the user
|
|
2685
|
+
* reaches for only when something went awry.
|
|
2686
|
+
*/
|
|
2687
|
+
|
|
2688
|
+
.lat-history {
|
|
2689
|
+
position: relative;
|
|
2690
|
+
display: inline-flex;
|
|
2691
|
+
align-items: center;
|
|
2692
|
+
gap: 2px;
|
|
2693
|
+
font-family: var(--lattice-font-family);
|
|
2694
|
+
font-size: var(--lattice-font-size-sm);
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
.lat-history button {
|
|
2698
|
+
display: inline-flex;
|
|
2699
|
+
align-items: center;
|
|
2700
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
2701
|
+
padding: calc(var(--lattice-space) * 0.5) var(--lattice-space);
|
|
2702
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
2703
|
+
border-radius: var(--lattice-radius-sm);
|
|
2704
|
+
background: var(--lattice-background);
|
|
2705
|
+
color: var(--lattice-foreground);
|
|
2706
|
+
font: inherit;
|
|
2707
|
+
cursor: pointer;
|
|
2708
|
+
transition: background var(--lattice-transition), border-color var(--lattice-transition);
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
.lat-history button:hover:not(:disabled) {
|
|
2712
|
+
background: var(--lattice-hovered-background);
|
|
2713
|
+
border-color: var(--lattice-border-strong);
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
/* Disabled rather than hidden: a control that vanishes when unavailable makes
|
|
2717
|
+
the toolbar jump, and the user loses the position they were aiming at. */
|
|
2718
|
+
.lat-history button:disabled {
|
|
2719
|
+
opacity: 0.45;
|
|
2720
|
+
cursor: default;
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
.lat-history__glyph {
|
|
2724
|
+
font-size: 1.1em;
|
|
2725
|
+
line-height: 1;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
.lat-history__toggle {
|
|
2729
|
+
padding-left: calc(var(--lattice-space) * 0.5);
|
|
2730
|
+
padding-right: calc(var(--lattice-space) * 0.5);
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
/* ------------------------------------------------------------------ */
|
|
2734
|
+
/* The timeline */
|
|
2735
|
+
/* ------------------------------------------------------------------ */
|
|
2736
|
+
|
|
2737
|
+
.lat-history__menu {
|
|
2738
|
+
position: absolute;
|
|
2739
|
+
top: 100%;
|
|
2740
|
+
left: 0;
|
|
2741
|
+
z-index: 20;
|
|
2742
|
+
min-width: 15em;
|
|
2743
|
+
max-height: 18em;
|
|
2744
|
+
overflow-y: auto;
|
|
2745
|
+
margin-top: 2px;
|
|
2746
|
+
padding: calc(var(--lattice-space) * 0.5);
|
|
2747
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
2748
|
+
border-radius: var(--lattice-radius);
|
|
2749
|
+
background: var(--lattice-surface);
|
|
2750
|
+
box-shadow: var(--lattice-popup-shadow);
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
.lat-history__item {
|
|
2754
|
+
display: flex;
|
|
2755
|
+
align-items: baseline;
|
|
2756
|
+
gap: var(--lattice-space);
|
|
2757
|
+
width: 100%;
|
|
2758
|
+
border: 0;
|
|
2759
|
+
border-radius: var(--lattice-radius-sm);
|
|
2760
|
+
background: none;
|
|
2761
|
+
text-align: left;
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
.lat-history__item:hover { background: var(--lattice-hovered-background); }
|
|
2765
|
+
|
|
2766
|
+
/* An entry that has been undone but not yet redone stays listed and reads as
|
|
2767
|
+
spent, so the redo stack is visible rather than implied. */
|
|
2768
|
+
.lat-history__item[data-undone='true'] {
|
|
2769
|
+
opacity: 0.5;
|
|
2770
|
+
font-style: italic;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
/* The action type, as a quiet monospace tag — it is a category, not prose. */
|
|
2774
|
+
.lat-history__kind {
|
|
2775
|
+
flex: 0 0 auto;
|
|
2776
|
+
min-width: 7em;
|
|
2777
|
+
font-family: var(--lattice-font-mono);
|
|
2778
|
+
font-size: var(--lattice-font-size-sm);
|
|
2779
|
+
color: var(--lattice-foreground-muted);
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
.lat-history__text {
|
|
2783
|
+
flex: 1 1 auto;
|
|
2784
|
+
overflow: hidden;
|
|
2785
|
+
text-overflow: ellipsis;
|
|
2786
|
+
white-space: nowrap;
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
.lat-history__empty {
|
|
2790
|
+
margin: 0;
|
|
2791
|
+
padding: calc(var(--lattice-space) * 0.5);
|
|
2792
|
+
color: var(--lattice-foreground-muted);
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2796
|
+
.lat-history button { transition: none; }
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
|
|
2800
|
+
/*
|
|
2801
|
+
* Diff and audit mode (§12).
|
|
2802
|
+
*
|
|
2803
|
+
* A row's status is a band down its leading edge; a changed cell is tinted with
|
|
2804
|
+
* a dotted underline. Two different devices on purpose — a reader scanning for
|
|
2805
|
+
* "which rows moved" and a reader asking "what changed in this row" are doing
|
|
2806
|
+
* different jobs, and one highlight cannot serve both without the row-level
|
|
2807
|
+
* signal drowning the cell-level one.
|
|
2808
|
+
*
|
|
2809
|
+
* Colour is never the only carrier: the row band has a distinct edge treatment
|
|
2810
|
+
* per status and the changed cell has its underline, so the states survive a
|
|
2811
|
+
* colour-blind reader and a monochrome print (§17).
|
|
2812
|
+
*/
|
|
2813
|
+
|
|
2814
|
+
/* No `position` is set on the marked rows themselves, deliberately. The
|
|
2815
|
+
virtualiser already positions every row — `absolute` in the scrolling body,
|
|
2816
|
+
`relative` in the sticky regions — and either one establishes the containing
|
|
2817
|
+
block the band below needs. Setting `position: relative` here matched the
|
|
2818
|
+
base rule's specificity and won on source order, which pulled marked rows out
|
|
2819
|
+
of the absolute layout and into normal flow: they kept their transform, so
|
|
2820
|
+
each one displaced the next by a whole row and the grid appeared to grow a
|
|
2821
|
+
gap for every changed row. */
|
|
2822
|
+
.lattice .lat-row--changed::before,
|
|
2823
|
+
.lattice .lat-row--added::before,
|
|
2824
|
+
.lattice .lat-row--removed::before {
|
|
2825
|
+
content: '';
|
|
2826
|
+
position: absolute;
|
|
2827
|
+
left: 0;
|
|
2828
|
+
top: 0;
|
|
2829
|
+
bottom: 0;
|
|
2830
|
+
width: 3px;
|
|
2831
|
+
z-index: 2;
|
|
2832
|
+
pointer-events: none;
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
.lattice .lat-row--changed::before { background: var(--lattice-variant-info-solid); }
|
|
2836
|
+
.lattice .lat-row--added::before { background: var(--lattice-variant-success-solid); }
|
|
2837
|
+
|
|
2838
|
+
/* Removed rows are struck through and dimmed: they are a record of something
|
|
2839
|
+
that is gone, not a row to act on. */
|
|
2840
|
+
.lattice .lat-row--removed::before { background: var(--lattice-variant-danger-solid); }
|
|
2841
|
+
|
|
2842
|
+
.lattice .lat-row--removed {
|
|
2843
|
+
opacity: 0.6;
|
|
2844
|
+
text-decoration: line-through;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
.lattice .lat-row--added { background: var(--lattice-variant-success-fill); }
|
|
2848
|
+
|
|
2849
|
+
/* ------------------------------------------------------------------ */
|
|
2850
|
+
|
|
2851
|
+
/* A changed cell. The underline is what makes it readable without colour.
|
|
2852
|
+
|
|
2853
|
+
The `.lat-row` in these two selectors is carrying real weight, not tidiness:
|
|
2854
|
+
`.lattice .lat-cell` sets `background-color: transparent`, which matches this
|
|
2855
|
+
specificity exactly and — living in lattice.css, concatenated after this file
|
|
2856
|
+
— wins on source order against the shorthand's colour. The box-shadow landed
|
|
2857
|
+
and the tint silently did not. The extra class outranks it in either order. */
|
|
2858
|
+
.lattice .lat-row .lat-cell--changed {
|
|
2859
|
+
background: var(--lattice-variant-info-fill);
|
|
2860
|
+
box-shadow: inset 0 -2px 0 -1px var(--lattice-variant-info-border);
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
/* A column the snapshot never had. Distinct from "changed", because a schema
|
|
2864
|
+
change is not an edit and lighting every row as changed-from-null turns an
|
|
2865
|
+
audit trail into noise. */
|
|
2866
|
+
.lattice .lat-row .lat-cell--added-column {
|
|
2867
|
+
background: var(--lattice-variant-neutral-fill);
|
|
2868
|
+
color: var(--lattice-foreground-muted);
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
/*
|
|
2872
|
+
* The prior value, shown beside the current one.
|
|
2873
|
+
*
|
|
2874
|
+
* Off by default — most audit views want the current value with the change
|
|
2875
|
+
* merely marked. `data-diff-before="show"` on the grid root turns it on for the
|
|
2876
|
+
* side-by-side reading that a regulator asks for.
|
|
2877
|
+
*/
|
|
2878
|
+
.lattice[data-diff-before='show'] .lat-cell--changed::after {
|
|
2879
|
+
content: attr(data-before);
|
|
2880
|
+
display: block;
|
|
2881
|
+
font-size: var(--lattice-font-size-sm);
|
|
2882
|
+
color: var(--lattice-variant-danger-text);
|
|
2883
|
+
text-decoration: line-through;
|
|
2884
|
+
white-space: nowrap;
|
|
2885
|
+
overflow: hidden;
|
|
2886
|
+
text-overflow: ellipsis;
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
.lattice[data-diff-before='show'] .lat-row--changed {
|
|
2890
|
+
/* The before line needs the room, or it clips against the row height. */
|
|
2891
|
+
align-items: flex-start;
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
|
|
2895
|
+
/*
|
|
2896
|
+
* Lattice Grid — saved views (§15, §16).
|
|
2897
|
+
*
|
|
2898
|
+
* The picker built by `packages/dom/src/viewpicker.js`. Kept in its own file
|
|
2899
|
+
* for the same reason the editors have one: it is an optional control, and a
|
|
2900
|
+
* host that never mounts it should be able to leave these rules out of its
|
|
2901
|
+
* bundle.
|
|
2902
|
+
*
|
|
2903
|
+
* Rules inherited from §16 and held to throughout:
|
|
2904
|
+
* · Every selector is scoped under `.lattice`, so the control cannot be
|
|
2905
|
+
* broken by host page CSS and cannot break it. The picker may be mounted
|
|
2906
|
+
* outside the grid's own root, so put the `lattice` class on whatever
|
|
2907
|
+
* wraps it — a dock, a dialog, a toolbar.
|
|
2908
|
+
* · No colour literal anywhere. Every value reads a custom property from
|
|
2909
|
+
* `lattice.css`, so a consumer retunes the control from the same place
|
|
2910
|
+
* they retune the grid, and dark mode comes for free.
|
|
2911
|
+
* · No `!important`.
|
|
2912
|
+
*
|
|
2913
|
+
* The one visual idea worth stating: a row is a *statement about the grid*,
|
|
2914
|
+
* not a menu entry. Name, badges and the line saying what applying it would
|
|
2915
|
+
* change all sit inside the same button, so the whole statement is one target
|
|
2916
|
+
* and one tab stop, and the actions sit outside it — pressing Delete must
|
|
2917
|
+
* never be one mis-aimed pixel away from rearranging the user's screen.
|
|
2918
|
+
*/
|
|
2919
|
+
|
|
2920
|
+
/* ------------------------------------------------------------------ */
|
|
2921
|
+
/* Root and heading */
|
|
2922
|
+
/* ------------------------------------------------------------------ */
|
|
2923
|
+
|
|
2924
|
+
.lattice .lat-views {
|
|
2925
|
+
display: flex;
|
|
2926
|
+
flex-direction: column;
|
|
2927
|
+
min-width: 0;
|
|
2928
|
+
box-sizing: border-box;
|
|
2929
|
+
color: var(--lattice-foreground);
|
|
2930
|
+
font-family: var(--lattice-font-family);
|
|
2931
|
+
font-size: var(--lattice-font-size);
|
|
2932
|
+
line-height: var(--lattice-line-height);
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
.lattice .lat-views__head {
|
|
2936
|
+
display: flex;
|
|
2937
|
+
align-items: baseline;
|
|
2938
|
+
justify-content: space-between;
|
|
2939
|
+
gap: var(--lattice-space);
|
|
2940
|
+
padding: var(--lattice-space) calc(var(--lattice-space) * 2);
|
|
2941
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
2942
|
+
background: var(--lattice-surface);
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
.lattice .lat-views__title {
|
|
2946
|
+
font-weight: var(--lattice-header-font-weight);
|
|
2947
|
+
color: var(--lattice-header-foreground);
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
/* The count is a pill rather than bare text so a zero reads as a state rather
|
|
2951
|
+
than as a stray character next to the title. */
|
|
2952
|
+
.lattice .lat-views__count {
|
|
2953
|
+
padding: var(--lattice-pill-padding-y) var(--lattice-pill-padding-x);
|
|
2954
|
+
border-radius: var(--lattice-radius-pill);
|
|
2955
|
+
background: var(--lattice-variant-neutral-fill);
|
|
2956
|
+
color: var(--lattice-variant-neutral-text);
|
|
2957
|
+
font-size: var(--lattice-font-size-sm);
|
|
2958
|
+
font-variant-numeric: tabular-nums;
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
.lattice .lat-views__empty {
|
|
2962
|
+
margin: 0;
|
|
2963
|
+
padding: calc(var(--lattice-space) * 3) calc(var(--lattice-space) * 2);
|
|
2964
|
+
color: var(--lattice-foreground-muted);
|
|
2965
|
+
font-size: var(--lattice-font-size-sm);
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
/* ------------------------------------------------------------------ */
|
|
2969
|
+
/* The list */
|
|
2970
|
+
/* ------------------------------------------------------------------ */
|
|
2971
|
+
|
|
2972
|
+
.lattice .lat-views__list {
|
|
2973
|
+
display: flex;
|
|
2974
|
+
flex: 1 1 auto;
|
|
2975
|
+
flex-direction: column;
|
|
2976
|
+
min-width: 0;
|
|
2977
|
+
overflow-y: auto;
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
/* The row stacks rather than sitting side by side. Four action buttons want
|
|
2981
|
+
about 180px and the dock is 260px wide before padding, which left the name
|
|
2982
|
+
and description column 26px across — the view's name wrapped to one
|
|
2983
|
+
character per line while its buttons had room to spare. Vertically there is
|
|
2984
|
+
as much space as the row needs. */
|
|
2985
|
+
.lattice .lat-views__item {
|
|
2986
|
+
display: flex;
|
|
2987
|
+
flex-direction: column;
|
|
2988
|
+
align-items: stretch;
|
|
2989
|
+
gap: calc(var(--lattice-space) * 0.25);
|
|
2990
|
+
min-width: 0;
|
|
2991
|
+
padding: calc(var(--lattice-space) * 0.5) var(--lattice-space);
|
|
2992
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
.lattice .lat-views__item:hover {
|
|
2996
|
+
background: var(--lattice-hovered-background);
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
/* The applied view is marked with a leading bar as well as a background, so
|
|
3000
|
+
the state survives a monochrome print and a colour-blind reader (§17). */
|
|
3001
|
+
.lattice .lat-views__item--active {
|
|
3002
|
+
background: var(--lattice-selected-background);
|
|
3003
|
+
box-shadow: inset var(--lattice-decoration-edge-width) 0 0 0 var(--lattice-accent);
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
.lattice .lat-views__item--active .lat-views__name {
|
|
3007
|
+
color: var(--lattice-selected-foreground);
|
|
3008
|
+
font-weight: var(--lattice-header-font-weight);
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
/* ------------------------------------------------------------------ */
|
|
3012
|
+
/* The row's statement: name, badges, description, diff */
|
|
3013
|
+
/* ------------------------------------------------------------------ */
|
|
3014
|
+
|
|
3015
|
+
.lattice .lat-views__apply {
|
|
3016
|
+
display: grid;
|
|
3017
|
+
width: 100%;
|
|
3018
|
+
/* Name and badges share the first line, everything else spans both columns
|
|
3019
|
+
underneath, so a long name pushes the badges rather than wrapping past
|
|
3020
|
+
them. */
|
|
3021
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
3022
|
+
gap: calc(var(--lattice-space) * 0.5) var(--lattice-space);
|
|
3023
|
+
flex: 1 1 auto;
|
|
3024
|
+
min-width: 0;
|
|
3025
|
+
padding: var(--lattice-space);
|
|
3026
|
+
border: 0;
|
|
3027
|
+
border-radius: var(--lattice-radius);
|
|
3028
|
+
background: none;
|
|
3029
|
+
color: inherit;
|
|
3030
|
+
font: inherit;
|
|
3031
|
+
text-align: left;
|
|
3032
|
+
cursor: pointer;
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
.lattice .lat-views__apply:focus-visible {
|
|
3036
|
+
outline: var(--lattice-focus-width) solid var(--lattice-focus-color);
|
|
3037
|
+
outline-offset: var(--lattice-focus-offset);
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
.lattice .lat-views__apply[disabled] {
|
|
3041
|
+
cursor: default;
|
|
3042
|
+
color: var(--lattice-foreground-muted);
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
.lattice .lat-views__name {
|
|
3046
|
+
min-width: 0;
|
|
3047
|
+
overflow: hidden;
|
|
3048
|
+
text-overflow: ellipsis;
|
|
3049
|
+
white-space: nowrap;
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
.lattice .lat-views__badges {
|
|
3053
|
+
display: flex;
|
|
3054
|
+
flex: none;
|
|
3055
|
+
align-items: center;
|
|
3056
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
.lattice .lat-views__badge {
|
|
3060
|
+
padding: var(--lattice-pill-padding-y) var(--lattice-pill-padding-x);
|
|
3061
|
+
border: var(--lattice-border-width) solid transparent;
|
|
3062
|
+
border-radius: var(--lattice-radius-pill);
|
|
3063
|
+
font-size: var(--lattice-font-size-sm);
|
|
3064
|
+
white-space: nowrap;
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
.lattice .lat-views__badge--default {
|
|
3068
|
+
border-color: var(--lattice-variant-info-border);
|
|
3069
|
+
background: var(--lattice-variant-info-fill);
|
|
3070
|
+
color: var(--lattice-variant-info-text);
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
.lattice .lat-views__badge--shared {
|
|
3074
|
+
border-color: var(--lattice-variant-success-border);
|
|
3075
|
+
background: var(--lattice-variant-success-fill);
|
|
3076
|
+
color: var(--lattice-variant-success-text);
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
/* Private is the common case, so it is stated quietly — a border and muted
|
|
3080
|
+
text rather than a filled pill competing with the name beside it. */
|
|
3081
|
+
.lattice .lat-views__badge--private {
|
|
3082
|
+
border-color: var(--lattice-border-color);
|
|
3083
|
+
color: var(--lattice-foreground-muted);
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
.lattice .lat-views__description,
|
|
3087
|
+
.lattice .lat-views__diff {
|
|
3088
|
+
grid-column: 1 / -1;
|
|
3089
|
+
min-width: 0;
|
|
3090
|
+
font-size: var(--lattice-font-size-sm);
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
.lattice .lat-views__description {
|
|
3094
|
+
color: var(--lattice-foreground-muted);
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
/* What applying this view would do. Warning-toned because it is a heads-up,
|
|
3098
|
+
not an error: the view is fine, it simply changes things. */
|
|
3099
|
+
.lattice .lat-views__diff {
|
|
3100
|
+
padding: var(--lattice-pill-padding-y) var(--lattice-pill-padding-x);
|
|
3101
|
+
border-radius: var(--lattice-radius-sm);
|
|
3102
|
+
background: var(--lattice-variant-warning-fill);
|
|
3103
|
+
color: var(--lattice-variant-warning-text);
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
.lattice .lat-views__diff--clean {
|
|
3107
|
+
padding-left: 0;
|
|
3108
|
+
background: none;
|
|
3109
|
+
color: var(--lattice-foreground-muted);
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
/* ------------------------------------------------------------------ */
|
|
3113
|
+
/* Actions */
|
|
3114
|
+
/* ------------------------------------------------------------------ */
|
|
3115
|
+
|
|
3116
|
+
.lattice .lat-views__actions,
|
|
3117
|
+
.lattice .lat-views__confirm {
|
|
3118
|
+
display: flex;
|
|
3119
|
+
flex: none;
|
|
3120
|
+
/* Wraps, so a narrow dock drops "Delete" onto a second line instead of
|
|
3121
|
+
pushing it out of the panel. */
|
|
3122
|
+
flex-wrap: wrap;
|
|
3123
|
+
align-items: center;
|
|
3124
|
+
justify-content: flex-end;
|
|
3125
|
+
gap: calc(var(--lattice-space) * 0.5);
|
|
3126
|
+
padding: 0 0 calc(var(--lattice-space) * 0.5);
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
.lattice .lat-views__confirm {
|
|
3130
|
+
flex: 1 1 auto;
|
|
3131
|
+
flex-wrap: wrap;
|
|
3132
|
+
min-width: 0;
|
|
3133
|
+
padding-left: var(--lattice-space);
|
|
3134
|
+
font-size: var(--lattice-font-size-sm);
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
/* A row being renamed gives the whole width to the box, since the name it is
|
|
3138
|
+
replacing is the only thing on that line anyway. */
|
|
3139
|
+
.lattice .lat-views__item--editing {
|
|
3140
|
+
background: var(--lattice-hovered-background);
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
.lattice .lat-views__item--editing .lat-views__input {
|
|
3144
|
+
flex: 1 1 auto;
|
|
3145
|
+
min-width: 0;
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
.lattice .lat-views__action {
|
|
3149
|
+
flex: none;
|
|
3150
|
+
padding: 0;
|
|
3151
|
+
border: 0;
|
|
3152
|
+
background: none;
|
|
3153
|
+
color: var(--lattice-link-color);
|
|
3154
|
+
font: inherit;
|
|
3155
|
+
font-size: var(--lattice-font-size-sm);
|
|
3156
|
+
text-decoration: underline;
|
|
3157
|
+
white-space: nowrap;
|
|
3158
|
+
cursor: pointer;
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
.lattice .lat-views__action:hover {
|
|
3162
|
+
color: var(--lattice-accent);
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
.lattice .lat-views__action:focus-visible {
|
|
3166
|
+
outline: var(--lattice-focus-width) solid var(--lattice-focus-color);
|
|
3167
|
+
outline-offset: var(--lattice-focus-offset);
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
.lattice .lat-views__action--danger {
|
|
3171
|
+
color: var(--lattice-variant-danger-solid);
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
/* Actions stay out of the way until the row is under the pointer or holds
|
|
3175
|
+
focus, so a list of ten views reads as ten names rather than forty links.
|
|
3176
|
+
Focus-within is what keeps them reachable by keyboard. */
|
|
3177
|
+
.lattice .lat-views__item .lat-views__actions {
|
|
3178
|
+
opacity: 0;
|
|
3179
|
+
transition: opacity var(--lattice-transition);
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
.lattice .lat-views__item:hover .lat-views__actions,
|
|
3183
|
+
.lattice .lat-views__item:focus-within .lat-views__actions,
|
|
3184
|
+
.lattice .lat-views__item--active .lat-views__actions {
|
|
3185
|
+
opacity: 1;
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
/* Hiding by opacity alone would leave the links clickable while invisible, so
|
|
3189
|
+
hosts that honour reduced motion get them permanently visible instead of a
|
|
3190
|
+
fade that never runs. */
|
|
3191
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3192
|
+
.lattice .lat-views__item .lat-views__actions {
|
|
3193
|
+
opacity: 1;
|
|
3194
|
+
transition: none;
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
/* ------------------------------------------------------------------ */
|
|
3199
|
+
/* Save-as footer and the rename box */
|
|
3200
|
+
/* ------------------------------------------------------------------ */
|
|
3201
|
+
|
|
3202
|
+
.lattice .lat-views__foot {
|
|
3203
|
+
display: flex;
|
|
3204
|
+
flex: none;
|
|
3205
|
+
align-items: center;
|
|
3206
|
+
gap: var(--lattice-space);
|
|
3207
|
+
padding: var(--lattice-space) calc(var(--lattice-space) * 2);
|
|
3208
|
+
border-top: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
3209
|
+
background: var(--lattice-surface);
|
|
3210
|
+
}
|
|
3211
|
+
|
|
3212
|
+
.lattice .lat-views__input {
|
|
3213
|
+
flex: 1 1 auto;
|
|
3214
|
+
min-width: 0;
|
|
3215
|
+
box-sizing: border-box;
|
|
3216
|
+
padding: calc(var(--lattice-space) * 0.75) var(--lattice-space);
|
|
3217
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
3218
|
+
border-radius: var(--lattice-radius-sm);
|
|
3219
|
+
background: var(--lattice-background);
|
|
3220
|
+
color: var(--lattice-foreground);
|
|
3221
|
+
font: inherit;
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
.lattice .lat-views__input:focus-visible {
|
|
3225
|
+
outline: var(--lattice-focus-width) solid var(--lattice-focus-color);
|
|
3226
|
+
outline-offset: 0;
|
|
3227
|
+
border-color: var(--lattice-accent);
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
.lattice .lat-views__input::placeholder {
|
|
3231
|
+
color: var(--lattice-foreground-muted);
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
.lattice .lat-views__save {
|
|
3235
|
+
flex: none;
|
|
3236
|
+
padding: calc(var(--lattice-space) * 0.75) calc(var(--lattice-space) * 2);
|
|
3237
|
+
border: var(--lattice-border-width) solid var(--lattice-accent);
|
|
3238
|
+
border-radius: var(--lattice-radius-sm);
|
|
3239
|
+
background: var(--lattice-accent);
|
|
3240
|
+
color: var(--lattice-accent-contrast);
|
|
3241
|
+
font: inherit;
|
|
3242
|
+
cursor: pointer;
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
.lattice .lat-views__save:hover {
|
|
3246
|
+
border-color: var(--lattice-focus-color);
|
|
3247
|
+
background: var(--lattice-focus-color);
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
.lattice .lat-views__save:focus-visible {
|
|
3251
|
+
outline: var(--lattice-focus-width) solid var(--lattice-focus-color);
|
|
3252
|
+
outline-offset: 1px;
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
/* ------------------------------------------------------------------ */
|
|
3256
|
+
/* Density (§16) */
|
|
3257
|
+
/* ------------------------------------------------------------------ */
|
|
3258
|
+
|
|
3259
|
+
.lattice[data-density='compact'] .lat-views__item {
|
|
3260
|
+
padding-top: 0;
|
|
3261
|
+
padding-bottom: 0;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
.lattice[data-density='comfortable'] .lat-views__item {
|
|
3265
|
+
padding-top: var(--lattice-space);
|
|
3266
|
+
padding-bottom: var(--lattice-space);
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
/* ------------------------------------------------------------------ */
|
|
3270
|
+
/* Groups: the developer's views, then the user's own */
|
|
3271
|
+
/* ------------------------------------------------------------------ */
|
|
3272
|
+
|
|
3273
|
+
/* The second heading bar sits mid-panel rather than at the top, so it needs a
|
|
3274
|
+
rule above it to close the block before it. */
|
|
3275
|
+
.lattice .lat-views__list + .lat-views__head,
|
|
3276
|
+
.lattice .lat-views__empty + .lat-views__head {
|
|
3277
|
+
border-top: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
/* Icon actions. Sized to the same 24px square as the rail's buttons so the two
|
|
3281
|
+
sets of controls in one panel read as one family. */
|
|
3282
|
+
.lattice .lat-views__action-icon {
|
|
3283
|
+
width: 14px;
|
|
3284
|
+
height: 14px;
|
|
3285
|
+
pointer-events: none;
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
.lattice .lat-views__action:has(.lat-views__action-icon) {
|
|
3289
|
+
display: inline-flex;
|
|
3290
|
+
align-items: center;
|
|
3291
|
+
justify-content: center;
|
|
3292
|
+
width: 24px;
|
|
3293
|
+
height: 24px;
|
|
3294
|
+
padding: 0;
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
/* A built-in row has no action strip, so its name and description should use
|
|
3298
|
+
the height the buttons would have taken rather than leaving a gap. */
|
|
3299
|
+
.lattice .lat-views__item:not(:has(.lat-views__actions)) .lat-views__apply {
|
|
3300
|
+
padding-bottom: calc(var(--lattice-space) * 0.5);
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
|
|
3304
|
+
/*
|
|
3305
|
+
* Lattice Grid — the prompt bar.
|
|
3306
|
+
*
|
|
3307
|
+
* A one-line input above the grid, plus the preview of the plan a model
|
|
3308
|
+
* proposed. The layout follows the four states the component writes onto its
|
|
3309
|
+
* root as `data-state`: `idle`, `busy`, `preview`, `error`. Styling from the
|
|
3310
|
+
* state attribute rather than from modifier classes keeps the JavaScript to a
|
|
3311
|
+
* single `setAttribute` per transition, which is what lets the input keep focus
|
|
3312
|
+
* while the bar changes around it.
|
|
3313
|
+
*
|
|
3314
|
+
* Two rules, the same two the rest of the theme keeps:
|
|
3315
|
+
*
|
|
3316
|
+
* 1. **Colour comes from tokens, never from literals**, so a host restyling the
|
|
3317
|
+
* grid restyles this with it and dark mode needs no second stylesheet. The
|
|
3318
|
+
* preview borrows the `info` variant tokens because it is a notice about
|
|
3319
|
+
* something that has not happened yet; the error line borrows `danger`.
|
|
3320
|
+
* 2. **The bar is chrome, not a row.** It sits outside the virtualised viewport
|
|
3321
|
+
* and is free to be as tall as its content, so the preview can wrap without
|
|
3322
|
+
* the layout arithmetic in `structure.css` caring.
|
|
3323
|
+
*/
|
|
3324
|
+
|
|
3325
|
+
/* ------------------------------------------------------------------ *
|
|
3326
|
+
* The bar
|
|
3327
|
+
* ------------------------------------------------------------------ */
|
|
3328
|
+
|
|
3329
|
+
.lattice .lat-prompt {
|
|
3330
|
+
box-sizing: border-box;
|
|
3331
|
+
display: flex;
|
|
3332
|
+
flex-direction: column;
|
|
3333
|
+
gap: calc(var(--lattice-space) * 0.75);
|
|
3334
|
+
padding: var(--lattice-space) calc(var(--lattice-space) * 2);
|
|
3335
|
+
border-bottom: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
3336
|
+
background: var(--lattice-surface);
|
|
3337
|
+
color: var(--lattice-foreground);
|
|
3338
|
+
font-family: var(--lattice-font-family);
|
|
3339
|
+
font-size: var(--lattice-font-size);
|
|
3340
|
+
line-height: var(--lattice-line-height);
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
.lattice .lat-prompt__row {
|
|
3344
|
+
display: flex;
|
|
3345
|
+
align-items: center;
|
|
3346
|
+
gap: var(--lattice-space);
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
.lattice .lat-prompt__input {
|
|
3350
|
+
box-sizing: border-box;
|
|
3351
|
+
flex: 1 1 auto;
|
|
3352
|
+
min-width: 0;
|
|
3353
|
+
height: calc(var(--lattice-row-height) - var(--lattice-space));
|
|
3354
|
+
padding: 0 calc(var(--lattice-space) * 1.5);
|
|
3355
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
3356
|
+
border-radius: var(--lattice-radius);
|
|
3357
|
+
background: var(--lattice-background);
|
|
3358
|
+
color: inherit;
|
|
3359
|
+
font: inherit;
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
.lattice .lat-prompt__input::placeholder {
|
|
3363
|
+
color: var(--lattice-foreground-muted);
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
.lattice .lat-prompt__input:focus-visible,
|
|
3367
|
+
.lattice .lat-prompt button:focus-visible {
|
|
3368
|
+
outline: var(--lattice-focus-width) solid var(--lattice-focus-color);
|
|
3369
|
+
outline-offset: var(--lattice-focus-offset);
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
/* ------------------------------------------------------------------ *
|
|
3373
|
+
* Buttons
|
|
3374
|
+
* ------------------------------------------------------------------ */
|
|
3375
|
+
|
|
3376
|
+
.lattice .lat-prompt button {
|
|
3377
|
+
flex: none;
|
|
3378
|
+
height: calc(var(--lattice-row-height) - var(--lattice-space));
|
|
3379
|
+
padding: 0 calc(var(--lattice-space) * 2.5);
|
|
3380
|
+
border: var(--lattice-border-width) solid var(--lattice-border-color);
|
|
3381
|
+
border-radius: var(--lattice-radius);
|
|
3382
|
+
background: var(--lattice-background);
|
|
3383
|
+
color: inherit;
|
|
3384
|
+
font: inherit;
|
|
3385
|
+
cursor: pointer;
|
|
3386
|
+
transition: background var(--lattice-transition), border-color var(--lattice-transition);
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
.lattice .lat-prompt button:hover:not(:disabled) {
|
|
3390
|
+
background: var(--lattice-hovered-background);
|
|
3391
|
+
border-color: var(--lattice-border-strong);
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
.lattice .lat-prompt button:disabled {
|
|
3395
|
+
opacity: 0.5;
|
|
3396
|
+
cursor: default;
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3399
|
+
/* Submit and Apply are the affirmative action in their row, so each is the one
|
|
3400
|
+
filled control there — never two at once, which is why Discard stays plain. */
|
|
3401
|
+
.lattice .lat-prompt__submit,
|
|
3402
|
+
.lattice .lat-prompt__apply {
|
|
3403
|
+
border-color: var(--lattice-accent);
|
|
3404
|
+
background: var(--lattice-accent);
|
|
3405
|
+
color: var(--lattice-accent-contrast);
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
.lattice .lat-prompt__submit:hover:not(:disabled),
|
|
3409
|
+
.lattice .lat-prompt__apply:hover:not(:disabled) {
|
|
3410
|
+
background: var(--lattice-accent);
|
|
3411
|
+
border-color: var(--lattice-accent);
|
|
3412
|
+
filter: brightness(1.08);
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
/* ------------------------------------------------------------------ *
|
|
3416
|
+
* The preview
|
|
3417
|
+
* ------------------------------------------------------------------ */
|
|
3418
|
+
|
|
3419
|
+
/* Nothing has been applied yet, and the preview has to say so at a glance —
|
|
3420
|
+
hence a notice, tinted like an `info` badge, rather than a quiet caption that
|
|
3421
|
+
reads as a result. */
|
|
3422
|
+
.lattice .lat-prompt__preview {
|
|
3423
|
+
display: flex;
|
|
3424
|
+
flex-direction: column;
|
|
3425
|
+
gap: calc(var(--lattice-space) * 0.75);
|
|
3426
|
+
padding: calc(var(--lattice-space) * 1.5) calc(var(--lattice-space) * 2);
|
|
3427
|
+
border: var(--lattice-border-width) solid var(--lattice-variant-info-border);
|
|
3428
|
+
border-radius: var(--lattice-radius);
|
|
3429
|
+
background: var(--lattice-variant-info-fill);
|
|
3430
|
+
color: var(--lattice-variant-info-text);
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
.lattice .lat-prompt__preview[hidden],
|
|
3434
|
+
.lattice .lat-prompt__notes[hidden],
|
|
3435
|
+
.lattice .lat-prompt__error[hidden] {
|
|
3436
|
+
display: none;
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
.lattice .lat-prompt__summary {
|
|
3440
|
+
margin: 0;
|
|
3441
|
+
font-weight: var(--lattice-header-font-weight);
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
/* What the model asked for and did not get. Muted, because it qualifies the
|
|
3445
|
+
summary rather than competing with it — but present, because a user
|
|
3446
|
+
approving three of five conditions must be able to see the other two. */
|
|
3447
|
+
.lattice .lat-prompt__notes {
|
|
3448
|
+
margin: 0;
|
|
3449
|
+
padding-left: calc(var(--lattice-space) * 4);
|
|
3450
|
+
font-size: var(--lattice-font-size-sm);
|
|
3451
|
+
color: var(--lattice-foreground-muted);
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
.lattice .lat-prompt__note {
|
|
3455
|
+
margin: calc(var(--lattice-space) * 0.25) 0;
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
.lattice .lat-prompt__buttons {
|
|
3459
|
+
display: flex;
|
|
3460
|
+
gap: var(--lattice-space);
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
/* ------------------------------------------------------------------ *
|
|
3464
|
+
* Status and error
|
|
3465
|
+
* ------------------------------------------------------------------ */
|
|
3466
|
+
|
|
3467
|
+
.lattice .lat-prompt__status {
|
|
3468
|
+
margin: 0;
|
|
3469
|
+
min-height: 1em;
|
|
3470
|
+
font-size: var(--lattice-font-size-sm);
|
|
3471
|
+
color: var(--lattice-foreground-muted);
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
.lattice .lat-prompt__status:empty {
|
|
3475
|
+
display: none;
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
.lattice .lat-prompt__error {
|
|
3479
|
+
margin: 0;
|
|
3480
|
+
padding: var(--lattice-space) calc(var(--lattice-space) * 1.5);
|
|
3481
|
+
border: var(--lattice-border-width) solid var(--lattice-invalid-border);
|
|
3482
|
+
border-radius: var(--lattice-radius-sm);
|
|
3483
|
+
background: var(--lattice-invalid-background);
|
|
3484
|
+
color: var(--lattice-variant-danger-text);
|
|
3485
|
+
font-size: var(--lattice-font-size-sm);
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
/* ------------------------------------------------------------------ *
|
|
3489
|
+
* States
|
|
3490
|
+
* ------------------------------------------------------------------ */
|
|
3491
|
+
|
|
3492
|
+
/* The wait is the whole reason the bar needs a busy state: a model takes
|
|
3493
|
+
seconds, which is long enough for a user to conclude nothing happened and
|
|
3494
|
+
press Enter again. The bar is dimmed and its controls disabled, so the answer
|
|
3495
|
+
to "did it take my question?" is visible without reading the status line. */
|
|
3496
|
+
.lattice .lat-prompt[data-state="busy"] .lat-prompt__row {
|
|
3497
|
+
opacity: 0.65;
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
.lattice .lat-prompt[data-state="busy"] .lat-prompt__input {
|
|
3501
|
+
cursor: progress;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3505
|
+
.lattice .lat-prompt button {
|
|
3506
|
+
transition: none;
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
|
|
3511
|
+
/* =========================================================================
|
|
3512
|
+
Lattice Grid — theme (spec section 16)
|
|
3513
|
+
TOCLOCO Inc.
|
|
3514
|
+
|
|
3515
|
+
Rules this file holds itself to, all of them from section 16:
|
|
3516
|
+
|
|
3517
|
+
1. CSS custom properties only. No preprocessor, no runtime style injection
|
|
3518
|
+
beyond the single stylesheet the cell layer maintains for generated
|
|
3519
|
+
decoration classes.
|
|
3520
|
+
2. A base theme plus light and dark variants, switched by a data attribute
|
|
3521
|
+
on the root: `data-theme="light" | "dark"`. With no attribute the system
|
|
3522
|
+
preference decides.
|
|
3523
|
+
3. Roughly sixty documented variables, listed and commented below.
|
|
3524
|
+
4. A compact density preset and a comfortable one, both derived from the
|
|
3525
|
+
spacing unit: `data-density="compact" | "comfortable"`.
|
|
3526
|
+
5. Zero use of `!important`. Not once. If a rule here loses to a host page
|
|
3527
|
+
rule, the fix is a more specific selector, not a bigger hammer.
|
|
3528
|
+
6. Every selector is namespaced under the single root class `.lattice`, so
|
|
3529
|
+
the grid cannot be broken by host page CSS and cannot break it.
|
|
3530
|
+
Coexisting with DHTMLX styles on the same page is a hard acceptance
|
|
3531
|
+
test (section 16), which is why no element, attribute or bare class
|
|
3532
|
+
selector appears anywhere in this file.
|
|
3533
|
+
7. Built-in variant tokens meet WCAG AA contrast in both themes; the values
|
|
3534
|
+
below are checked by `test/cell.test.js`, which parses this file.
|
|
3535
|
+
========================================================================= */
|
|
3536
|
+
|
|
3537
|
+
/* -------------------------------------------------------------------------
|
|
3538
|
+
1. Tokens — light (the base theme)
|
|
3539
|
+
------------------------------------------------------------------------- */
|
|
3540
|
+
|
|
3541
|
+
/*
|
|
3542
|
+
* `.lattice-tokens` carries the palette without the grid's own layout.
|
|
3543
|
+
*
|
|
3544
|
+
* Components that mount into the *host's* chrome rather than inside the grid —
|
|
3545
|
+
* the undo toolbar, a view picker in an application menu — need every
|
|
3546
|
+
* `--lattice-*` value and none of `display: flex; flex-direction: column`.
|
|
3547
|
+
* Borrowing the `.lattice` class for the tokens dragged the layout along with
|
|
3548
|
+
* them and stacked a row of buttons vertically.
|
|
3549
|
+
*/
|
|
3550
|
+
.lattice,
|
|
3551
|
+
.lattice-tokens {
|
|
3552
|
+
/* --- Spacing and geometry -------------------------------------------- */
|
|
3553
|
+
--lattice-space: 4px; /* the unit every density derives from */
|
|
3554
|
+
--lattice-row-height: 32px; /* fixed row height (spec 7.4 mode 1) */
|
|
3555
|
+
--lattice-header-height: 36px; /* header row height */
|
|
3556
|
+
--lattice-cell-padding-x: calc(var(--lattice-space) * 2);
|
|
3557
|
+
--lattice-cell-padding-y: 0px;
|
|
3558
|
+
--lattice-indent-size: calc(var(--lattice-space) * 5); /* tree indent per level */
|
|
3559
|
+
--lattice-gap: var(--lattice-space); /* gap between a decoration and its text */
|
|
3560
|
+
|
|
3561
|
+
/* --- Typography ------------------------------------------------------- */
|
|
3562
|
+
--lattice-font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
3563
|
+
--lattice-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
3564
|
+
--lattice-font-size: 13px;
|
|
3565
|
+
--lattice-font-size-sm: 11px;
|
|
3566
|
+
--lattice-font-size-lg: 15px;
|
|
3567
|
+
--lattice-header-font-size: 13px;
|
|
3568
|
+
--lattice-font-weight: 400;
|
|
3569
|
+
--lattice-header-font-weight: 600;
|
|
3570
|
+
--lattice-line-height: 1.4;
|
|
3571
|
+
|
|
3572
|
+
/* --- Surfaces and text ------------------------------------------------ */
|
|
3573
|
+
--lattice-background: #ffffff; /* body background */
|
|
3574
|
+
--lattice-surface: #f7f8f9; /* header, totals, status bar */
|
|
3575
|
+
--lattice-surface-alt: #fbfcfc; /* striped rows */
|
|
3576
|
+
--lattice-foreground: #1c2126; /* body text */
|
|
3577
|
+
--lattice-foreground-muted: #5b6670; /* secondary text, counts, placeholders */
|
|
3578
|
+
--lattice-header-foreground: #2e3338;
|
|
3579
|
+
|
|
3580
|
+
/* --- Lines ------------------------------------------------------------ */
|
|
3581
|
+
--lattice-border-color: #dfe3e6;
|
|
3582
|
+
--lattice-border-width: 1px;
|
|
3583
|
+
--lattice-border-strong: #c8cfd5; /* header underline, pinned edges */
|
|
3584
|
+
|
|
3585
|
+
/* --- Interaction ------------------------------------------------------ */
|
|
3586
|
+
--lattice-accent: #1a6bc7; /* the one brand colour the grid uses */
|
|
3587
|
+
--lattice-accent-contrast: #ffffff; /* text on the accent */
|
|
3588
|
+
--lattice-hovered-background: #f2f5f8;
|
|
3589
|
+
--lattice-selected-background: #dceaf9;
|
|
3590
|
+
--lattice-selected-foreground: #10437a;
|
|
3591
|
+
--lattice-range-background: #e7f1fd;
|
|
3592
|
+
--lattice-range-border: #1a6bc7;
|
|
3593
|
+
--lattice-invalid-background: #fdeaea;
|
|
3594
|
+
--lattice-invalid-border: #c22b2b;
|
|
3595
|
+
--lattice-link-color: #10437a;
|
|
3596
|
+
|
|
3597
|
+
/* --- Focus (spec 17: visible, and independent of selection styling) ---- */
|
|
3598
|
+
--lattice-focus-color: #1a6bc7;
|
|
3599
|
+
--lattice-focus-width: 2px;
|
|
3600
|
+
--lattice-focus-offset: -2px;
|
|
3601
|
+
|
|
3602
|
+
/* --- Depth ------------------------------------------------------------ */
|
|
3603
|
+
--lattice-pinned-shadow: 2px 0 4px -2px rgba(16, 20, 24, 0.28);
|
|
3604
|
+
--lattice-pinned-shadow-end: -2px 0 4px -2px rgba(16, 20, 24, 0.28);
|
|
3605
|
+
--lattice-overlay-background: rgba(255, 255, 255, 0.72);
|
|
3606
|
+
--lattice-popup-shadow: 0 6px 16px -4px rgba(16, 20, 24, 0.24);
|
|
3607
|
+
|
|
3608
|
+
/* --- Radii ------------------------------------------------------------ */
|
|
3609
|
+
--lattice-radius: 4px;
|
|
3610
|
+
--lattice-radius-sm: 2px;
|
|
3611
|
+
--lattice-radius-pill: 999px;
|
|
3612
|
+
|
|
3613
|
+
/* --- Decoration geometry (spec 8.7: shape, never colour) --------------- */
|
|
3614
|
+
--lattice-decoration-edge-width: 3px; /* the `fill` decoration's leading bar */
|
|
3615
|
+
--lattice-pill-padding-x: calc(var(--lattice-space) * 2);
|
|
3616
|
+
--lattice-pill-padding-y: calc(var(--lattice-space) / 2);
|
|
3617
|
+
--lattice-pill-font-size: var(--lattice-font-size-sm);
|
|
3618
|
+
--lattice-dot-size: 8px;
|
|
3619
|
+
--lattice-bar-height: 8px;
|
|
3620
|
+
--lattice-bar-min-width: 48px;
|
|
3621
|
+
--lattice-icon-size: 16px;
|
|
3622
|
+
|
|
3623
|
+
/* --- Placeholders and motion ------------------------------------------ */
|
|
3624
|
+
--lattice-skeleton-color: #e9ecef;
|
|
3625
|
+
--lattice-skeleton-highlight: #f5f7f8;
|
|
3626
|
+
--lattice-transition: 120ms ease;
|
|
3627
|
+
--lattice-flash-background: #fdf3e2;
|
|
3628
|
+
|
|
3629
|
+
/* --- Variant tokens (spec 8.7). fill / text / border / solid ----------- */
|
|
3630
|
+
/* `solid` is the strong marker colour used by `dot` and `bar`. */
|
|
3631
|
+
--lattice-variant-neutral-fill: #eff1f3;
|
|
3632
|
+
--lattice-variant-neutral-text: #2e3338;
|
|
3633
|
+
--lattice-variant-neutral-border: #c8cfd5;
|
|
3634
|
+
--lattice-variant-neutral-solid: #5b6670;
|
|
3635
|
+
|
|
3636
|
+
--lattice-variant-info-fill: #e7f1fd;
|
|
3637
|
+
--lattice-variant-info-text: #10437a;
|
|
3638
|
+
--lattice-variant-info-border: #b6d4f5;
|
|
3639
|
+
--lattice-variant-info-solid: #1a6bc7;
|
|
3640
|
+
|
|
3641
|
+
--lattice-variant-success-fill: #e6f4ea;
|
|
3642
|
+
--lattice-variant-success-text: #14532d;
|
|
3643
|
+
--lattice-variant-success-border: #b7e0c4;
|
|
3644
|
+
--lattice-variant-success-solid: #1b7f3b;
|
|
3645
|
+
|
|
3646
|
+
--lattice-variant-warning-fill: #fdf3e2;
|
|
3647
|
+
--lattice-variant-warning-text: #6b3f09;
|
|
3648
|
+
--lattice-variant-warning-border: #f3d9a8;
|
|
3649
|
+
--lattice-variant-warning-solid: #a96a0b;
|
|
3650
|
+
|
|
3651
|
+
--lattice-variant-danger-fill: #fdeaea;
|
|
3652
|
+
--lattice-variant-danger-text: #7a1414;
|
|
3653
|
+
--lattice-variant-danger-border: #f3c0c0;
|
|
3654
|
+
--lattice-variant-danger-solid: #c22b2b;
|
|
3655
|
+
|
|
3656
|
+
--lattice-variant-accent-fill: #edebfd;
|
|
3657
|
+
--lattice-variant-accent-text: #382a8c;
|
|
3658
|
+
--lattice-variant-accent-border: #cfc7f7;
|
|
3659
|
+
--lattice-variant-accent-solid: #5a46d6;
|
|
3660
|
+
|
|
3661
|
+
/* `none` suppresses decoration for a value; it paints nothing at all. */
|
|
3662
|
+
--lattice-variant-none-fill: transparent;
|
|
3663
|
+
--lattice-variant-none-text: inherit;
|
|
3664
|
+
--lattice-variant-none-border: transparent;
|
|
3665
|
+
--lattice-variant-none-solid: transparent;
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3668
|
+
/* -------------------------------------------------------------------------
|
|
3669
|
+
2. Tokens — dark
|
|
3670
|
+
|
|
3671
|
+
Declared twice on purpose: once behind the explicit attribute, once behind
|
|
3672
|
+
the system preference for hosts that never set one. The preference block is
|
|
3673
|
+
guarded so an explicit `data-theme="light"` still wins on a dark desktop.
|
|
3674
|
+
------------------------------------------------------------------------- */
|
|
3675
|
+
|
|
3676
|
+
.lattice[data-theme="dark"] {
|
|
3677
|
+
--lattice-background: #14181c;
|
|
3678
|
+
--lattice-surface: #1b2026;
|
|
3679
|
+
--lattice-surface-alt: #171c21;
|
|
3680
|
+
--lattice-foreground: #e4e9ee;
|
|
3681
|
+
--lattice-foreground-muted: #99a4ae;
|
|
3682
|
+
--lattice-header-foreground: #d5dce3;
|
|
3683
|
+
|
|
3684
|
+
--lattice-border-color: #2a3138;
|
|
3685
|
+
--lattice-border-strong: #3a434c;
|
|
3686
|
+
|
|
3687
|
+
--lattice-accent: #4e9bea;
|
|
3688
|
+
--lattice-accent-contrast: #06101b;
|
|
3689
|
+
--lattice-hovered-background: #1f252c;
|
|
3690
|
+
--lattice-selected-background: #17304c;
|
|
3691
|
+
--lattice-selected-foreground: #cfe3fb;
|
|
3692
|
+
--lattice-range-background: #142838;
|
|
3693
|
+
--lattice-range-border: #4e9bea;
|
|
3694
|
+
--lattice-invalid-background: #351515;
|
|
3695
|
+
--lattice-invalid-border: #e05b5b;
|
|
3696
|
+
--lattice-link-color: #8cc0f5;
|
|
3697
|
+
|
|
3698
|
+
--lattice-focus-color: #6cb0f2;
|
|
3699
|
+
|
|
3700
|
+
--lattice-pinned-shadow: 2px 0 4px -2px rgba(0, 0, 0, 0.6);
|
|
3701
|
+
--lattice-pinned-shadow-end: -2px 0 4px -2px rgba(0, 0, 0, 0.6);
|
|
3702
|
+
--lattice-overlay-background: rgba(20, 24, 28, 0.72);
|
|
3703
|
+
--lattice-popup-shadow: 0 6px 16px -4px rgba(0, 0, 0, 0.6);
|
|
3704
|
+
|
|
3705
|
+
--lattice-skeleton-color: #232a31;
|
|
3706
|
+
--lattice-skeleton-highlight: #2b333b;
|
|
3707
|
+
--lattice-flash-background: #33240d;
|
|
3708
|
+
|
|
3709
|
+
--lattice-variant-neutral-fill: #262c33;
|
|
3710
|
+
--lattice-variant-neutral-text: #d5dce3;
|
|
3711
|
+
--lattice-variant-neutral-border: #3a434c;
|
|
3712
|
+
--lattice-variant-neutral-solid: #8c98a4;
|
|
3713
|
+
|
|
3714
|
+
--lattice-variant-info-fill: #10263d;
|
|
3715
|
+
--lattice-variant-info-text: #a8cdf7;
|
|
3716
|
+
--lattice-variant-info-border: #1e4370;
|
|
3717
|
+
--lattice-variant-info-solid: #4e9bea;
|
|
3718
|
+
|
|
3719
|
+
--lattice-variant-success-fill: #102a1a;
|
|
3720
|
+
--lattice-variant-success-text: #9bdcb2;
|
|
3721
|
+
--lattice-variant-success-border: #1e4a2e;
|
|
3722
|
+
--lattice-variant-success-solid: #3fa45f;
|
|
3723
|
+
|
|
3724
|
+
--lattice-variant-warning-fill: #33240d;
|
|
3725
|
+
--lattice-variant-warning-text: #f0c782;
|
|
3726
|
+
--lattice-variant-warning-border: #573d16;
|
|
3727
|
+
--lattice-variant-warning-solid: #d08b1e;
|
|
3728
|
+
|
|
3729
|
+
--lattice-variant-danger-fill: #351515;
|
|
3730
|
+
--lattice-variant-danger-text: #f3afaf;
|
|
3731
|
+
--lattice-variant-danger-border: #5c2626;
|
|
3732
|
+
--lattice-variant-danger-solid: #e05b5b;
|
|
3733
|
+
|
|
3734
|
+
--lattice-variant-accent-fill: #211c4a;
|
|
3735
|
+
--lattice-variant-accent-text: #c3b9f8;
|
|
3736
|
+
--lattice-variant-accent-border: #3a3180;
|
|
3737
|
+
--lattice-variant-accent-solid: #7c68e8;
|
|
3738
|
+
}
|
|
3739
|
+
|
|
3740
|
+
@media (prefers-color-scheme: dark) {
|
|
3741
|
+
.lattice:not([data-theme="light"]) {
|
|
3742
|
+
--lattice-background: #14181c;
|
|
3743
|
+
--lattice-surface: #1b2026;
|
|
3744
|
+
--lattice-surface-alt: #171c21;
|
|
3745
|
+
--lattice-foreground: #e4e9ee;
|
|
3746
|
+
--lattice-foreground-muted: #99a4ae;
|
|
3747
|
+
--lattice-header-foreground: #d5dce3;
|
|
3748
|
+
|
|
3749
|
+
--lattice-border-color: #2a3138;
|
|
3750
|
+
--lattice-border-strong: #3a434c;
|
|
3751
|
+
|
|
3752
|
+
--lattice-accent: #4e9bea;
|
|
3753
|
+
--lattice-accent-contrast: #06101b;
|
|
3754
|
+
--lattice-hovered-background: #1f252c;
|
|
3755
|
+
--lattice-selected-background: #17304c;
|
|
3756
|
+
--lattice-selected-foreground: #cfe3fb;
|
|
3757
|
+
--lattice-range-background: #142838;
|
|
3758
|
+
--lattice-range-border: #4e9bea;
|
|
3759
|
+
--lattice-invalid-background: #351515;
|
|
3760
|
+
--lattice-invalid-border: #e05b5b;
|
|
3761
|
+
--lattice-link-color: #8cc0f5;
|
|
3762
|
+
|
|
3763
|
+
--lattice-focus-color: #6cb0f2;
|
|
3764
|
+
|
|
3765
|
+
--lattice-pinned-shadow: 2px 0 4px -2px rgba(0, 0, 0, 0.6);
|
|
3766
|
+
--lattice-pinned-shadow-end: -2px 0 4px -2px rgba(0, 0, 0, 0.6);
|
|
3767
|
+
--lattice-overlay-background: rgba(20, 24, 28, 0.72);
|
|
3768
|
+
--lattice-popup-shadow: 0 6px 16px -4px rgba(0, 0, 0, 0.6);
|
|
3769
|
+
|
|
3770
|
+
--lattice-skeleton-color: #232a31;
|
|
3771
|
+
--lattice-skeleton-highlight: #2b333b;
|
|
3772
|
+
--lattice-flash-background: #33240d;
|
|
3773
|
+
|
|
3774
|
+
--lattice-variant-neutral-fill: #262c33;
|
|
3775
|
+
--lattice-variant-neutral-text: #d5dce3;
|
|
3776
|
+
--lattice-variant-neutral-border: #3a434c;
|
|
3777
|
+
--lattice-variant-neutral-solid: #8c98a4;
|
|
3778
|
+
|
|
3779
|
+
--lattice-variant-info-fill: #10263d;
|
|
3780
|
+
--lattice-variant-info-text: #a8cdf7;
|
|
3781
|
+
--lattice-variant-info-border: #1e4370;
|
|
3782
|
+
--lattice-variant-info-solid: #4e9bea;
|
|
3783
|
+
|
|
3784
|
+
--lattice-variant-success-fill: #102a1a;
|
|
3785
|
+
--lattice-variant-success-text: #9bdcb2;
|
|
3786
|
+
--lattice-variant-success-border: #1e4a2e;
|
|
3787
|
+
--lattice-variant-success-solid: #3fa45f;
|
|
3788
|
+
|
|
3789
|
+
--lattice-variant-warning-fill: #33240d;
|
|
3790
|
+
--lattice-variant-warning-text: #f0c782;
|
|
3791
|
+
--lattice-variant-warning-border: #573d16;
|
|
3792
|
+
--lattice-variant-warning-solid: #d08b1e;
|
|
3793
|
+
|
|
3794
|
+
--lattice-variant-danger-fill: #351515;
|
|
3795
|
+
--lattice-variant-danger-text: #f3afaf;
|
|
3796
|
+
--lattice-variant-danger-border: #5c2626;
|
|
3797
|
+
--lattice-variant-danger-solid: #e05b5b;
|
|
3798
|
+
|
|
3799
|
+
--lattice-variant-accent-fill: #211c4a;
|
|
3800
|
+
--lattice-variant-accent-text: #c3b9f8;
|
|
3801
|
+
--lattice-variant-accent-border: #3a3180;
|
|
3802
|
+
--lattice-variant-accent-solid: #7c68e8;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
/* -------------------------------------------------------------------------
|
|
3807
|
+
3. Density presets
|
|
3808
|
+
|
|
3809
|
+
Both derive from the spacing unit, so retuning one number retunes the grid.
|
|
3810
|
+
------------------------------------------------------------------------- */
|
|
3811
|
+
|
|
3812
|
+
.lattice[data-density="compact"] {
|
|
3813
|
+
--lattice-space: 3px;
|
|
3814
|
+
--lattice-row-height: 26px;
|
|
3815
|
+
--lattice-header-height: 30px;
|
|
3816
|
+
--lattice-font-size: 12px;
|
|
3817
|
+
--lattice-pill-padding-x: calc(var(--lattice-space) * 2);
|
|
3818
|
+
--lattice-pill-padding-y: 0px;
|
|
3819
|
+
--lattice-dot-size: 7px;
|
|
3820
|
+
--lattice-bar-height: 6px;
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
.lattice[data-density="comfortable"] {
|
|
3824
|
+
--lattice-space: 5px;
|
|
3825
|
+
--lattice-row-height: 44px;
|
|
3826
|
+
--lattice-header-height: 48px;
|
|
3827
|
+
--lattice-font-size: 14px;
|
|
3828
|
+
--lattice-pill-padding-x: calc(var(--lattice-space) * 2.5);
|
|
3829
|
+
--lattice-pill-padding-y: calc(var(--lattice-space) / 2);
|
|
3830
|
+
--lattice-dot-size: 9px;
|
|
3831
|
+
--lattice-bar-height: 10px;
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
/* -------------------------------------------------------------------------
|
|
3835
|
+
4. Root and structure
|
|
3836
|
+
|
|
3837
|
+
`all: revert` is deliberately not used: it is a blunt instrument that also
|
|
3838
|
+
discards the browser default the grid relies on. Instead every property the
|
|
3839
|
+
grid depends on is set explicitly, which is what makes coexistence with a
|
|
3840
|
+
host framework's global styles predictable.
|
|
3841
|
+
------------------------------------------------------------------------- */
|
|
3842
|
+
|
|
3843
|
+
.lattice {
|
|
3844
|
+
box-sizing: border-box;
|
|
3845
|
+
position: relative;
|
|
3846
|
+
display: flex;
|
|
3847
|
+
flex-direction: column;
|
|
3848
|
+
overflow: hidden;
|
|
3849
|
+
font-family: var(--lattice-font-family);
|
|
3850
|
+
font-size: var(--lattice-font-size);
|
|
3851
|
+
font-weight: var(--lattice-font-weight);
|
|
3852
|
+
line-height: var(--lattice-line-height);
|
|
3853
|
+
color: var(--lattice-foreground);
|
|
3854
|
+
background-color: var(--lattice-background);
|
|
3855
|
+
-webkit-font-smoothing: antialiased;
|
|
3856
|
+
text-align: start;
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
.lattice *,
|
|
3860
|
+
.lattice *::before,
|
|
3861
|
+
.lattice *::after {
|
|
3862
|
+
box-sizing: inherit;
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
/* -------------------------------------------------------------------------
|
|
3866
|
+
5. Cells — the default path (spec 7.6)
|
|
3867
|
+
|
|
3868
|
+
A plain cell is one element with one text node. Everything below adds to
|
|
3869
|
+
that; nothing replaces it.
|
|
3870
|
+
------------------------------------------------------------------------- */
|
|
3871
|
+
|
|
3872
|
+
.lattice .lat-cell {
|
|
3873
|
+
display: flex;
|
|
3874
|
+
align-items: center;
|
|
3875
|
+
gap: var(--lattice-gap);
|
|
3876
|
+
min-width: 0;
|
|
3877
|
+
height: 100%;
|
|
3878
|
+
padding-block: var(--lattice-cell-padding-y);
|
|
3879
|
+
padding-inline: var(--lattice-cell-padding-x);
|
|
3880
|
+
overflow: hidden;
|
|
3881
|
+
white-space: nowrap;
|
|
3882
|
+
text-overflow: ellipsis;
|
|
3883
|
+
color: inherit;
|
|
3884
|
+
background-color: transparent;
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
.lattice .lat-cell--center { justify-content: center; text-align: center; }
|
|
3888
|
+
.lattice .lat-cell--end { justify-content: flex-end; text-align: end; }
|
|
3889
|
+
|
|
3890
|
+
.lattice .lat-cell--wrap {
|
|
3891
|
+
white-space: normal;
|
|
3892
|
+
align-items: flex-start;
|
|
3893
|
+
padding-block: calc(var(--lattice-space) * 1.5);
|
|
3894
|
+
text-overflow: clip;
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3897
|
+
/* Numeric columns read better with tabular figures; this costs nothing. */
|
|
3898
|
+
.lattice .lat-cell--end { font-variant-numeric: tabular-nums; }
|
|
3899
|
+
|
|
3900
|
+
/* The holder for an explicitly permitted `{{{ }}}` template segment. */
|
|
3901
|
+
.lattice .lat-raw { display: contents; }
|
|
3902
|
+
|
|
3903
|
+
/* -------------------------------------------------------------------------
|
|
3904
|
+
6. Decorations (spec 8.7)
|
|
3905
|
+
|
|
3906
|
+
Layout, padding and radius live here, as static classes. Colour arrives via
|
|
3907
|
+
the one class per decoration/variant pair that the cell layer generates and
|
|
3908
|
+
injects, which sets background, colour and border-colour from the variant
|
|
3909
|
+
tokens above. Between them there is nothing left for a per-cell inline
|
|
3910
|
+
style to do.
|
|
3911
|
+
------------------------------------------------------------------------- */
|
|
3912
|
+
|
|
3913
|
+
.lattice .lat-dec { gap: var(--lattice-gap); }
|
|
3914
|
+
|
|
3915
|
+
.lattice .lat-dec-text { overflow: hidden; text-overflow: ellipsis; }
|
|
3916
|
+
|
|
3917
|
+
/* --- fill --- */
|
|
3918
|
+
.lattice .lat-dec--fill {
|
|
3919
|
+
/* The tint is applied by the generated pair class; this only guarantees the
|
|
3920
|
+
tint reaches the cell's edges rather than stopping at the text. */
|
|
3921
|
+
background-clip: padding-box;
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
.lattice .lat-dec--fill.lat-dec--edge { background-color: transparent; }
|
|
3925
|
+
|
|
3926
|
+
/* --- pill --- */
|
|
3927
|
+
.lattice .lat-pill {
|
|
3928
|
+
display: inline-flex;
|
|
3929
|
+
align-items: center;
|
|
3930
|
+
max-width: 100%;
|
|
3931
|
+
padding-block: var(--lattice-pill-padding-y);
|
|
3932
|
+
padding-inline: var(--lattice-pill-padding-x);
|
|
3933
|
+
border: var(--lattice-border-width) solid transparent;
|
|
3934
|
+
border-radius: var(--lattice-radius-pill);
|
|
3935
|
+
font-size: var(--lattice-pill-font-size);
|
|
3936
|
+
font-weight: 500;
|
|
3937
|
+
line-height: 1.5;
|
|
3938
|
+
white-space: nowrap;
|
|
3939
|
+
overflow: hidden;
|
|
3940
|
+
text-overflow: ellipsis;
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3943
|
+
.lattice .lat-dec--rounded .lat-pill,
|
|
3944
|
+
.lattice .lat-pill.lat-dec--rounded { border-radius: var(--lattice-radius); }
|
|
3945
|
+
|
|
3946
|
+
.lattice .lat-dec--square .lat-pill,
|
|
3947
|
+
.lattice .lat-pill.lat-dec--square { border-radius: 0; }
|
|
3948
|
+
|
|
3949
|
+
.lattice .lat-dec--sm .lat-pill,
|
|
3950
|
+
.lattice .lat-pill.lat-dec--sm {
|
|
3951
|
+
font-size: var(--lattice-font-size-sm);
|
|
3952
|
+
padding-inline: var(--lattice-space);
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
.lattice .lat-dec--lg .lat-pill,
|
|
3956
|
+
.lattice .lat-pill.lat-dec--lg {
|
|
3957
|
+
font-size: var(--lattice-font-size);
|
|
3958
|
+
padding-inline: calc(var(--lattice-space) * 3);
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
/* --- dot --- */
|
|
3962
|
+
.lattice .lat-dot {
|
|
3963
|
+
flex: none;
|
|
3964
|
+
width: var(--lattice-dot-size);
|
|
3965
|
+
height: var(--lattice-dot-size);
|
|
3966
|
+
border-radius: var(--lattice-radius-pill);
|
|
3967
|
+
background-color: var(--lattice-foreground-muted);
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
.lattice .lat-dec--dot.lat-dec--end,
|
|
3971
|
+
.lattice .lat-dec--icon.lat-dec--end { flex-direction: row-reverse; }
|
|
3972
|
+
|
|
3973
|
+
.lattice .lat-dec--dot.lat-dec--end .lat-dec-text,
|
|
3974
|
+
.lattice .lat-dec--icon.lat-dec--end .lat-dec-text { margin-inline-end: auto; }
|
|
3975
|
+
|
|
3976
|
+
/* --- bar --- */
|
|
3977
|
+
.lattice .lat-bar {
|
|
3978
|
+
position: relative;
|
|
3979
|
+
flex: 1 1 var(--lattice-bar-min-width);
|
|
3980
|
+
min-width: var(--lattice-bar-min-width);
|
|
3981
|
+
height: var(--lattice-bar-height);
|
|
3982
|
+
border-radius: var(--lattice-radius-sm);
|
|
3983
|
+
overflow: hidden;
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3986
|
+
.lattice .lat-bar-fill {
|
|
3987
|
+
position: absolute;
|
|
3988
|
+
inset-block: 0;
|
|
3989
|
+
inset-inline-start: var(--lat-bar-start, 0%);
|
|
3990
|
+
width: var(--lat-bar-size, 0%);
|
|
3991
|
+
border-radius: inherit;
|
|
3992
|
+
background-color: var(--lattice-foreground-muted);
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3995
|
+
.lattice .lat-dec--no-track .lat-bar { background-color: transparent; }
|
|
3996
|
+
|
|
3997
|
+
.lattice .lat-dec--bar .lat-dec-text {
|
|
3998
|
+
flex: none;
|
|
3999
|
+
min-width: 4ch;
|
|
4000
|
+
text-align: end;
|
|
4001
|
+
font-variant-numeric: tabular-nums;
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
.lattice .lat-dec--value-inside { position: relative; }
|
|
4005
|
+
|
|
4006
|
+
.lattice .lat-dec--value-inside .lat-bar { flex: 1 1 auto; }
|
|
4007
|
+
|
|
4008
|
+
.lattice .lat-dec--value-inside .lat-dec-text {
|
|
4009
|
+
position: absolute;
|
|
4010
|
+
inset-inline: var(--lattice-cell-padding-x);
|
|
4011
|
+
text-align: center;
|
|
4012
|
+
pointer-events: none;
|
|
4013
|
+
}
|
|
4014
|
+
|
|
4015
|
+
/* --- heat --- */
|
|
4016
|
+
.lattice .lat-dec--heat { background-clip: padding-box; }
|
|
4017
|
+
|
|
4018
|
+
/* --- icon --- */
|
|
4019
|
+
.lattice .lat-icon {
|
|
4020
|
+
flex: none;
|
|
4021
|
+
width: var(--lattice-icon-size);
|
|
4022
|
+
height: var(--lattice-icon-size);
|
|
4023
|
+
color: inherit;
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
.lattice .lat-icon-cell { display: inline-flex; align-items: center; gap: var(--lattice-gap); min-width: 0; }
|
|
4027
|
+
|
|
4028
|
+
/* --- none: suppresses the decoration for a value (spec 8.7) --- */
|
|
4029
|
+
.lattice .lat-dec--none .lat-pill,
|
|
4030
|
+
.lattice .lat-pill.lat-dec--none {
|
|
4031
|
+
padding: 0;
|
|
4032
|
+
border-color: transparent;
|
|
4033
|
+
background-color: transparent;
|
|
4034
|
+
color: inherit;
|
|
4035
|
+
font-size: inherit;
|
|
4036
|
+
font-weight: inherit;
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
.lattice .lat-dec--none .lat-dot,
|
|
4040
|
+
.lattice .lat-dec--none .lat-icon { visibility: hidden; }
|
|
4041
|
+
|
|
4042
|
+
.lattice .lat-dec--none.lat-dec--fill,
|
|
4043
|
+
.lattice .lat-dec--none.lat-dec--heat { background-color: transparent; }
|
|
4044
|
+
|
|
4045
|
+
/* -------------------------------------------------------------------------
|
|
4046
|
+
7. Static variant classes
|
|
4047
|
+
|
|
4048
|
+
Used by the component renderers (`pill`, `icon`, `progress`), which cannot
|
|
4049
|
+
ask the compiler for a generated pair class because they are constructed by
|
|
4050
|
+
name rather than compiled per column. The set is finite — seven tokens —
|
|
4051
|
+
so it costs a fixed number of rules rather than growing with the columns.
|
|
4052
|
+
------------------------------------------------------------------------- */
|
|
4053
|
+
|
|
4054
|
+
.lattice .lat-v-neutral { background-color: var(--lattice-variant-neutral-fill); color: var(--lattice-variant-neutral-text); border-color: var(--lattice-variant-neutral-border); }
|
|
4055
|
+
.lattice .lat-v-info { background-color: var(--lattice-variant-info-fill); color: var(--lattice-variant-info-text); border-color: var(--lattice-variant-info-border); }
|
|
4056
|
+
.lattice .lat-v-success { background-color: var(--lattice-variant-success-fill); color: var(--lattice-variant-success-text); border-color: var(--lattice-variant-success-border); }
|
|
4057
|
+
.lattice .lat-v-warning { background-color: var(--lattice-variant-warning-fill); color: var(--lattice-variant-warning-text); border-color: var(--lattice-variant-warning-border); }
|
|
4058
|
+
.lattice .lat-v-danger { background-color: var(--lattice-variant-danger-fill); color: var(--lattice-variant-danger-text); border-color: var(--lattice-variant-danger-border); }
|
|
4059
|
+
.lattice .lat-v-accent { background-color: var(--lattice-variant-accent-fill); color: var(--lattice-variant-accent-text); border-color: var(--lattice-variant-accent-border); }
|
|
4060
|
+
|
|
4061
|
+
.lattice .lat-dec--outline.lat-v-neutral,
|
|
4062
|
+
.lattice .lat-dec--outline.lat-v-info,
|
|
4063
|
+
.lattice .lat-dec--outline.lat-v-success,
|
|
4064
|
+
.lattice .lat-dec--outline.lat-v-warning,
|
|
4065
|
+
.lattice .lat-dec--outline.lat-v-danger,
|
|
4066
|
+
.lattice .lat-dec--outline.lat-v-accent { background-color: transparent; }
|
|
4067
|
+
|
|
4068
|
+
/* -------------------------------------------------------------------------
|
|
4069
|
+
8. Built-in renderers (spec 8.8)
|
|
4070
|
+
------------------------------------------------------------------------- */
|
|
4071
|
+
|
|
4072
|
+
/* --- group and tree expander ------------------------------------------- */
|
|
4073
|
+
|
|
4074
|
+
.lattice .lat-group {
|
|
4075
|
+
display: flex;
|
|
4076
|
+
align-items: center;
|
|
4077
|
+
gap: var(--lattice-gap);
|
|
4078
|
+
min-width: 0;
|
|
4079
|
+
/* One custom property carries depth. The guides are drawn from it, so a row
|
|
4080
|
+
changing level costs one property write and no relayout of inline styles. */
|
|
4081
|
+
padding-inline-start: calc(var(--lat-indent, 0) * var(--lattice-indent-size));
|
|
4082
|
+
background-image: repeating-linear-gradient(
|
|
4083
|
+
to right,
|
|
4084
|
+
var(--lattice-border-color) 0 var(--lattice-border-width),
|
|
4085
|
+
transparent var(--lattice-border-width) var(--lattice-indent-size)
|
|
4086
|
+
);
|
|
4087
|
+
background-repeat: no-repeat;
|
|
4088
|
+
background-size: calc(var(--lat-indent, 0) * var(--lattice-indent-size)) 100%;
|
|
4089
|
+
}
|
|
4090
|
+
|
|
4091
|
+
.lattice .lat-group[data-level="0"] { background-image: none; }
|
|
4092
|
+
|
|
4093
|
+
.lattice .lat-expander {
|
|
4094
|
+
display: inline-flex;
|
|
4095
|
+
align-items: center;
|
|
4096
|
+
justify-content: center;
|
|
4097
|
+
flex: none;
|
|
4098
|
+
width: calc(var(--lattice-space) * 4);
|
|
4099
|
+
height: calc(var(--lattice-space) * 4);
|
|
4100
|
+
padding: 0;
|
|
4101
|
+
border: 0;
|
|
4102
|
+
border-radius: var(--lattice-radius-sm);
|
|
4103
|
+
background: transparent;
|
|
4104
|
+
color: var(--lattice-foreground-muted);
|
|
4105
|
+
font: inherit;
|
|
4106
|
+
cursor: pointer;
|
|
4107
|
+
}
|
|
4108
|
+
|
|
4109
|
+
.lattice .lat-expander:hover { background-color: var(--lattice-hovered-background); color: var(--lattice-foreground); }
|
|
4110
|
+
|
|
4111
|
+
.lattice .lat-expander[data-state="leaf"] { visibility: hidden; cursor: default; }
|
|
4112
|
+
|
|
4113
|
+
.lattice .lat-expander-icon { transition: transform var(--lattice-transition); }
|
|
4114
|
+
|
|
4115
|
+
.lattice .lat-group-label { overflow: hidden; text-overflow: ellipsis; }
|
|
4116
|
+
|
|
4117
|
+
.lattice .lat-group-count { flex: none; color: var(--lattice-foreground-muted); font-variant-numeric: tabular-nums; }
|
|
4118
|
+
|
|
4119
|
+
.lattice .lat-group-count:empty { display: none; }
|
|
4120
|
+
|
|
4121
|
+
/* --- checkbox ----------------------------------------------------------- */
|
|
4122
|
+
|
|
4123
|
+
.lattice .lat-checkbox { display: inline-flex; align-items: center; }
|
|
4124
|
+
|
|
4125
|
+
.lattice .lat-checkbox-input {
|
|
4126
|
+
width: calc(var(--lattice-space) * 3.5);
|
|
4127
|
+
height: calc(var(--lattice-space) * 3.5);
|
|
4128
|
+
margin: 0;
|
|
4129
|
+
accent-color: var(--lattice-accent);
|
|
4130
|
+
cursor: pointer;
|
|
4131
|
+
}
|
|
4132
|
+
|
|
4133
|
+
.lattice .lat-checkbox-input:disabled { cursor: default; opacity: 0.55; }
|
|
4134
|
+
|
|
4135
|
+
/* --- progress ----------------------------------------------------------- */
|
|
4136
|
+
|
|
4137
|
+
.lattice .lat-progress { display: flex; align-items: center; gap: var(--lattice-gap); min-width: 0; width: 100%; }
|
|
4138
|
+
|
|
4139
|
+
.lattice .lat-progress-track {
|
|
4140
|
+
position: relative;
|
|
4141
|
+
flex: 1 1 auto;
|
|
4142
|
+
min-width: var(--lattice-bar-min-width);
|
|
4143
|
+
height: var(--lattice-bar-height);
|
|
4144
|
+
border-radius: var(--lattice-radius-sm);
|
|
4145
|
+
background-color: var(--lattice-variant-neutral-fill);
|
|
4146
|
+
overflow: hidden;
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
.lattice .lat-progress-fill {
|
|
4150
|
+
position: absolute;
|
|
4151
|
+
inset-block: 0;
|
|
4152
|
+
inset-inline-start: 0;
|
|
4153
|
+
width: var(--lat-progress-size, 0%);
|
|
4154
|
+
border-radius: inherit;
|
|
4155
|
+
background-color: var(--lattice-accent);
|
|
4156
|
+
transition: width var(--lattice-transition);
|
|
4157
|
+
}
|
|
4158
|
+
|
|
4159
|
+
.lattice .lat-progress-label { flex: none; min-width: 4ch; text-align: end; font-variant-numeric: tabular-nums; }
|
|
4160
|
+
|
|
4161
|
+
.lattice .lat-progress[data-variant="neutral"] .lat-progress-fill { background-color: var(--lattice-variant-neutral-solid); }
|
|
4162
|
+
.lattice .lat-progress[data-variant="info"] .lat-progress-fill { background-color: var(--lattice-variant-info-solid); }
|
|
4163
|
+
.lattice .lat-progress[data-variant="success"] .lat-progress-fill { background-color: var(--lattice-variant-success-solid); }
|
|
4164
|
+
.lattice .lat-progress[data-variant="warning"] .lat-progress-fill { background-color: var(--lattice-variant-warning-solid); }
|
|
4165
|
+
.lattice .lat-progress[data-variant="danger"] .lat-progress-fill { background-color: var(--lattice-variant-danger-solid); }
|
|
4166
|
+
.lattice .lat-progress[data-variant="accent"] .lat-progress-fill { background-color: var(--lattice-variant-accent-solid); }
|
|
4167
|
+
|
|
4168
|
+
.lattice .lat-icon-cell[data-variant="neutral"] .lat-icon { color: var(--lattice-variant-neutral-solid); }
|
|
4169
|
+
.lattice .lat-icon-cell[data-variant="info"] .lat-icon { color: var(--lattice-variant-info-solid); }
|
|
4170
|
+
.lattice .lat-icon-cell[data-variant="success"] .lat-icon { color: var(--lattice-variant-success-solid); }
|
|
4171
|
+
.lattice .lat-icon-cell[data-variant="warning"] .lat-icon { color: var(--lattice-variant-warning-solid); }
|
|
4172
|
+
.lattice .lat-icon-cell[data-variant="danger"] .lat-icon { color: var(--lattice-variant-danger-solid); }
|
|
4173
|
+
.lattice .lat-icon-cell[data-variant="accent"] .lat-icon { color: var(--lattice-variant-accent-solid); }
|
|
4174
|
+
|
|
4175
|
+
/* --- link --------------------------------------------------------------- */
|
|
4176
|
+
|
|
4177
|
+
.lattice .lat-link {
|
|
4178
|
+
display: inline-flex;
|
|
4179
|
+
align-items: center;
|
|
4180
|
+
gap: calc(var(--lattice-space) / 2);
|
|
4181
|
+
min-width: 0;
|
|
4182
|
+
color: var(--lattice-link-color);
|
|
4183
|
+
text-decoration: underline;
|
|
4184
|
+
text-underline-offset: 2px;
|
|
4185
|
+
overflow: hidden;
|
|
4186
|
+
text-overflow: ellipsis;
|
|
4187
|
+
}
|
|
4188
|
+
|
|
4189
|
+
.lattice .lat-link:hover { text-decoration-thickness: 2px; }
|
|
4190
|
+
|
|
4191
|
+
.lattice .lat-link-icon { width: 12px; height: 12px; opacity: 0.7; }
|
|
4192
|
+
|
|
4193
|
+
/* --- skeleton ----------------------------------------------------------- */
|
|
4194
|
+
|
|
4195
|
+
.lattice .lat-skeleton { display: flex; align-items: center; width: 100%; }
|
|
4196
|
+
|
|
4197
|
+
.lattice .lat-skeleton-bar {
|
|
4198
|
+
width: var(--lat-skeleton-width, 60%);
|
|
4199
|
+
height: calc(var(--lattice-space) * 2.5);
|
|
4200
|
+
border-radius: var(--lattice-radius-sm);
|
|
4201
|
+
background-color: var(--lattice-skeleton-color);
|
|
4202
|
+
background-image: linear-gradient(
|
|
4203
|
+
90deg,
|
|
4204
|
+
var(--lattice-skeleton-color) 0%,
|
|
4205
|
+
var(--lattice-skeleton-highlight) 50%,
|
|
4206
|
+
var(--lattice-skeleton-color) 100%
|
|
4207
|
+
);
|
|
4208
|
+
background-size: 200% 100%;
|
|
4209
|
+
animation: lattice-shimmer 1.4s linear infinite;
|
|
4210
|
+
}
|
|
4211
|
+
|
|
4212
|
+
@keyframes lattice-shimmer {
|
|
4213
|
+
from { background-position: 200% 0; }
|
|
4214
|
+
to { background-position: -200% 0; }
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
/* -------------------------------------------------------------------------
|
|
4218
|
+
9. State (spec 12) and accessibility (spec 17)
|
|
4219
|
+
|
|
4220
|
+
Focus is a visible outline that does not depend on selection styling, so a
|
|
4221
|
+
focused cell inside a selected range is still unambiguous.
|
|
4222
|
+
------------------------------------------------------------------------- */
|
|
4223
|
+
|
|
4224
|
+
.lattice .lat-row--hovered .lat-cell { background-color: var(--lattice-hovered-background); }
|
|
4225
|
+
|
|
4226
|
+
.lattice .lat-row--selected .lat-cell,
|
|
4227
|
+
.lattice .lat-cell--selected {
|
|
4228
|
+
background-color: var(--lattice-selected-background);
|
|
4229
|
+
color: var(--lattice-selected-foreground);
|
|
4230
|
+
}
|
|
4231
|
+
|
|
4232
|
+
.lattice .lat-cell--range { background-color: var(--lattice-range-background); }
|
|
4233
|
+
|
|
4234
|
+
.lattice .lat-cell--range-edge { box-shadow: inset 0 0 0 1px var(--lattice-range-border); }
|
|
4235
|
+
|
|
4236
|
+
.lattice .lat-cell--invalid {
|
|
4237
|
+
background-color: var(--lattice-invalid-background);
|
|
4238
|
+
box-shadow: inset 0 0 0 1px var(--lattice-invalid-border);
|
|
4239
|
+
}
|
|
4240
|
+
|
|
4241
|
+
.lattice .lat-cell--flash { animation: lattice-flash 800ms ease-out; }
|
|
4242
|
+
|
|
4243
|
+
@keyframes lattice-flash {
|
|
4244
|
+
from { background-color: var(--lattice-flash-background); }
|
|
4245
|
+
to { background-color: transparent; }
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
.lattice .lat-cell:focus-visible,
|
|
4249
|
+
.lattice .lat-cell--focused {
|
|
4250
|
+
outline: var(--lattice-focus-width) solid var(--lattice-focus-color);
|
|
4251
|
+
outline-offset: var(--lattice-focus-offset);
|
|
4252
|
+
/* Above the neighbouring cells so the outline is not clipped by them. */
|
|
4253
|
+
z-index: 1;
|
|
4254
|
+
}
|
|
4255
|
+
|
|
4256
|
+
.lattice .lat-expander:focus-visible,
|
|
4257
|
+
.lattice .lat-checkbox-input:focus-visible,
|
|
4258
|
+
.lattice .lat-link:focus-visible {
|
|
4259
|
+
outline: var(--lattice-focus-width) solid var(--lattice-focus-color);
|
|
4260
|
+
outline-offset: 1px;
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
@media (prefers-reduced-motion: reduce) {
|
|
4264
|
+
.lattice .lat-skeleton-bar { animation: none; }
|
|
4265
|
+
.lattice .lat-cell--flash { animation: none; }
|
|
4266
|
+
.lattice .lat-progress-fill { transition: none; }
|
|
4267
|
+
.lattice .lat-expander-icon { transition: none; }
|
|
4268
|
+
}
|
|
4269
|
+
|
|
4270
|
+
@media (prefers-contrast: more) {
|
|
4271
|
+
.lattice {
|
|
4272
|
+
--lattice-border-color: var(--lattice-foreground);
|
|
4273
|
+
--lattice-focus-width: 3px;
|
|
4274
|
+
}
|
|
4275
|
+
.lattice .lat-pill { border-color: currentColor; }
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
/* -------------------------------------------------------------------------
|
|
4279
|
+
10. Print
|
|
4280
|
+
|
|
4281
|
+
Backgrounds are the first thing a printer drops, so printed decorations
|
|
4282
|
+
fall back to a border and the text itself — which is exactly the section 8.7
|
|
4283
|
+
accessibility rule ("colour is never the only signal") paying for itself.
|
|
4284
|
+
------------------------------------------------------------------------- */
|
|
4285
|
+
|
|
4286
|
+
@media print {
|
|
4287
|
+
.lattice {
|
|
4288
|
+
--lattice-background: #ffffff;
|
|
4289
|
+
--lattice-foreground: #000000;
|
|
4290
|
+
--lattice-border-color: #999999;
|
|
4291
|
+
overflow: visible;
|
|
4292
|
+
color: var(--lattice-foreground);
|
|
4293
|
+
background-color: var(--lattice-background);
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4296
|
+
.lattice .lat-cell {
|
|
4297
|
+
overflow: visible;
|
|
4298
|
+
white-space: normal;
|
|
4299
|
+
color: var(--lattice-foreground);
|
|
4300
|
+
}
|
|
4301
|
+
|
|
4302
|
+
.lattice .lat-pill {
|
|
4303
|
+
border-color: currentColor;
|
|
4304
|
+
background-color: transparent;
|
|
4305
|
+
color: inherit;
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
.lattice .lat-dec--fill,
|
|
4309
|
+
.lattice .lat-dec--heat { background-color: transparent; }
|
|
4310
|
+
|
|
4311
|
+
.lattice .lat-skeleton-bar { animation: none; background-image: none; }
|
|
4312
|
+
|
|
4313
|
+
.lattice .lat-expander,
|
|
4314
|
+
.lattice .lat-link-icon { display: none; }
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
/* Highlights (§8.7). The colour arrives as a custom property so one rule serves
|
|
4318
|
+
every colour a host asks for, and a theme can restyle the treatment — a
|
|
4319
|
+
border, an animation — without knowing which colours are in play. */
|
|
4320
|
+
.lattice .lat-cell--highlight {
|
|
4321
|
+
background-color: var(--lattice-cell-highlight, var(--lattice-flash-background));
|
|
4322
|
+
transition: background-color 160ms ease-out;
|
|
4323
|
+
}
|
|
4324
|
+
|
|
4325
|
+
/* Respecting the user's setting: a flash is decoration, and someone who has
|
|
4326
|
+
asked for less motion should get the colour without the fade. */
|
|
4327
|
+
@media (prefers-reduced-motion: reduce) {
|
|
4328
|
+
.lattice .lat-cell--highlight { transition: none; }
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
/* ------------------------------------------------------------------ */
|
|
4332
|
+
/* Trial watermark (§2) */
|
|
4333
|
+
/* ------------------------------------------------------------------ */
|
|
4334
|
+
|
|
4335
|
+
/* A corner label, not a wall. §2 is explicit that the failure mode to avoid is
|
|
4336
|
+
a broken production screen, so this covers the scrollbar gutter — where there
|
|
4337
|
+
is nothing to read — and passes pointer events through to the grid beneath
|
|
4338
|
+
it. Only the link itself is clickable. */
|
|
4339
|
+
/* The trial mark. Loud on purpose — see watermark.js — but never a wall: it
|
|
4340
|
+
takes no pointer events except on its own link, so every click and drag
|
|
4341
|
+
passes through to the grid underneath. */
|
|
4342
|
+
.lattice .lat-watermark {
|
|
4343
|
+
position: absolute;
|
|
4344
|
+
z-index: 6;
|
|
4345
|
+
padding: 10px 18px;
|
|
4346
|
+
border-radius: var(--lattice-radius-sm);
|
|
4347
|
+
background: var(--lattice-accent);
|
|
4348
|
+
box-shadow: 0 2px 10px rgb(0 0 0 / 25%);
|
|
4349
|
+
color: var(--lattice-accent-contrast);
|
|
4350
|
+
font-size: 15px;
|
|
4351
|
+
font-weight: 700;
|
|
4352
|
+
letter-spacing: 0.02em;
|
|
4353
|
+
line-height: 1.3;
|
|
4354
|
+
white-space: nowrap;
|
|
4355
|
+
pointer-events: none;
|
|
4356
|
+
opacity: 0.94;
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4359
|
+
/* One rule per corner. The element is rebuilt in place rather than animated
|
|
4360
|
+
across, so these only ever need to describe where it lands. */
|
|
4361
|
+
.lattice .lat-watermark--br { right: 0; bottom: 0; }
|
|
4362
|
+
.lattice .lat-watermark--bl { left: 0; bottom: 0; }
|
|
4363
|
+
.lattice .lat-watermark--tl { left: 0; top: 0; }
|
|
4364
|
+
.lattice .lat-watermark--tr { right: 0; top: 0; }
|
|
4365
|
+
|
|
4366
|
+
/* Fades in as it arrives, so a corner change reads as a move rather than as a
|
|
4367
|
+
flicker. Suppressed for anyone who has asked for less motion — the mark is
|
|
4368
|
+
equally visible without it. */
|
|
4369
|
+
@keyframes lat-watermark-in {
|
|
4370
|
+
from { opacity: 0; }
|
|
4371
|
+
to { opacity: 0.94; }
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
.lattice .lat-watermark { animation: lat-watermark-in 220ms ease-out; }
|
|
4375
|
+
|
|
4376
|
+
@media (prefers-reduced-motion: reduce) {
|
|
4377
|
+
.lattice .lat-watermark { animation: none; }
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
.lattice .lat-watermark__link {
|
|
4381
|
+
color: inherit;
|
|
4382
|
+
pointer-events: auto;
|
|
4383
|
+
text-decoration: underline;
|
|
4384
|
+
text-underline-offset: 2px;
|
|
4385
|
+
}
|
|
4386
|
+
|
|
4387
|
+
.lattice .lat-watermark__link:hover { text-decoration-thickness: 2px; }
|
|
4388
|
+
|
|
4389
|
+
@media print {
|
|
4390
|
+
/* A trial grid prints with its notice: an unlicensed export should not look
|
|
4391
|
+
like a licensed one. */
|
|
4392
|
+
.lattice .lat-watermark { opacity: 1; animation: none; }
|
|
4393
|
+
}
|