flexdesk 0.2.0 → 0.3.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/css/base.css +1484 -181
- package/css/flexdesk.css +1311 -18
- package/css/overrides.css +44 -0
- package/css/tokens.css +45 -0
- package/dist/charts.js +5 -3
- package/dist/charts.js.map +1 -1
- package/dist/{chunk-DVU44T77.js → chunk-ELXVW542.js} +196 -75
- package/dist/chunk-ELXVW542.js.map +7 -0
- package/dist/chunk-LH5TSOZW.js +1237 -0
- package/dist/chunk-LH5TSOZW.js.map +7 -0
- package/dist/{chunk-TLZUUFOE.js → chunk-O5OHMWBB.js} +10 -2
- package/dist/chunk-O5OHMWBB.js.map +7 -0
- package/dist/{chunk-CT4YXXLP.js → chunk-QIU5S2RU.js} +371 -73
- package/dist/chunk-QIU5S2RU.js.map +7 -0
- package/dist/chunk-QNQHQ24V.js +408 -0
- package/dist/chunk-QNQHQ24V.js.map +7 -0
- package/dist/{chunk-DRYCDMEG.js → chunk-XKDTIT4Q.js} +168 -12
- package/dist/chunk-XKDTIT4Q.js.map +7 -0
- package/dist/editor.js +3 -380
- package/dist/editor.js.map +3 -3
- package/dist/flexdesk.css +1311 -18
- package/dist/tiles.js +168 -41
- package/dist/tiles.js.map +2 -2
- package/dist/tokens.css +45 -0
- package/dist/widgets.js +44 -14
- package/dist/widgets.js.map +2 -2
- package/dist/wm.js +2983 -142
- package/dist/wm.js.map +4 -4
- package/package.json +3 -2
- package/src/charts/chart_types.js +167 -0
- package/src/charts/plotly_wrapper.js +178 -10
- package/src/editor/notebook_tab_bar.js +39 -3
- package/src/tiles/tile_base.js +143 -35
- package/src/tiles/tile_grid.js +52 -1
- package/src/tiling/command_palette.js +71 -18
- package/src/tiling/desktops.js +36 -12
- package/src/tiling/keymap.js +24 -4
- package/src/tiling/shell.js +135 -24
- package/src/tiling/tab_strip.js +184 -0
- package/src/tiling/tile_breadcrumb.js +34 -2
- package/src/tiling/tile_renderer.js +1386 -21
- package/src/tiling/tile_tab_menu.js +101 -0
- package/src/tiling/tile_tree.js +82 -0
- package/src/tiling/wm.js +2352 -74
- package/src/ui/components/action_dropdown.js +34 -3
- package/src/ui/components/autocomplete_field.js +65 -13
- package/src/ui/components/context_menu.js +79 -8
- package/src/ui/components/data_table.js +508 -84
- package/src/ui/components/managed_window.js +928 -36
- package/src/ui/components/modal.js +214 -8
- package/dist/chunk-CT4YXXLP.js.map +0 -7
- package/dist/chunk-DRYCDMEG.js.map +0 -7
- package/dist/chunk-DVU44T77.js.map +0 -7
- package/dist/chunk-TLZUUFOE.js.map +0 -7
- package/dist/chunk-UCJ2WD4D.js +0 -625
- package/dist/chunk-UCJ2WD4D.js.map +0 -7
package/css/flexdesk.css
CHANGED
|
@@ -587,6 +587,25 @@ body.twm-dt-col-resizing {
|
|
|
587
587
|
.twm-preview-table:not(.twm-preview-table--readonly) tr:hover td {
|
|
588
588
|
background: rgba(255, 255, 255, 0.04);
|
|
589
589
|
}
|
|
590
|
+
/* Selected rows. `_updateRowSelection()` (src/ui/components/data_table.js:2226)
|
|
591
|
+
has always toggled `.selected` on the row, and no stylesheet has ever
|
|
592
|
+
defined it — so `selectable: true`, the component's headline affordance,
|
|
593
|
+
has selected INVISIBLY in every consumer since the class was written.
|
|
594
|
+
Painted on `td` rather than `tr` to match the hover rule above: the zebra
|
|
595
|
+
stripe sits on `tr`, and a `tr` background would fight it at equal
|
|
596
|
+
specificity instead of covering it.
|
|
597
|
+
|
|
598
|
+
AND IT HAS TO OUT-SPECIFY HOVER, not merely follow it. Source order only
|
|
599
|
+
decides a TIE, and there is none here: the hover rule above is (0,3,2) —
|
|
600
|
+
`:not()` contributes the specificity of its argument — against (0,2,3) for
|
|
601
|
+
the plain form. Written that way, pointing at the row you had chosen
|
|
602
|
+
un-highlights it, which reads as the selection being lost on mouse-over. The
|
|
603
|
+
first selector below carries the same `:not()` and lands at (0,3,3); the
|
|
604
|
+
second keeps the readonly tables, which hover never claimed, working. */
|
|
605
|
+
.twm-preview-table:not(.twm-preview-table--readonly) tbody tr.selected td,
|
|
606
|
+
.twm-preview-table tbody tr.selected td {
|
|
607
|
+
background: var(--state-selected, rgba(0, 122, 204, 0.15));
|
|
608
|
+
}
|
|
590
609
|
/* Wizard mode: uniform column widths, no selection */
|
|
591
610
|
.twm-preview-table-wrap--wizard .twm-preview-table {
|
|
592
611
|
table-layout: auto;
|
|
@@ -882,7 +901,14 @@ body.twm-dt-col-resizing {
|
|
|
882
901
|
background: transparent;
|
|
883
902
|
border: none;
|
|
884
903
|
color: #7f7f7f;
|
|
885
|
-
padding:
|
|
904
|
+
padding: 0;
|
|
905
|
+
/* A BOX, not "whatever the glyph turned out to be". The rule had padding
|
|
906
|
+
* and no dimensions, so the button's size was the icon's size — and the
|
|
907
|
+
* icon's size was nobody's decision at all (see below). In a 35px top bar
|
|
908
|
+
* that produced a ~32px control that visibly outweighed everything beside
|
|
909
|
+
* it. */
|
|
910
|
+
width: 22px;
|
|
911
|
+
height: 22px;
|
|
886
912
|
border-radius: var(--radius-sm);
|
|
887
913
|
cursor: pointer;
|
|
888
914
|
display: flex;
|
|
@@ -890,6 +916,23 @@ body.twm-dt-col-resizing {
|
|
|
890
916
|
justify-content: center;
|
|
891
917
|
transition: background-color 0.2s ease, color 0.2s ease;
|
|
892
918
|
}
|
|
919
|
+
/* THE GLYPH SIZE CAME ACROSS AS NOTHING.
|
|
920
|
+
*
|
|
921
|
+
* EcoAgent sets it — `main_new.css:4700`, `.panel-toggle-btn
|
|
922
|
+
* .material-symbols-outlined { font-size: var(--font-size-xl) }` — and the
|
|
923
|
+
* extraction that produced this stylesheet took the button rule and left that
|
|
924
|
+
* one behind. Material Symbols then falls back to its own default of 24px, so
|
|
925
|
+
* every consumer of the library has been drawing this control half again as
|
|
926
|
+
* large as its author intended. Same family as the `--ea-*` variables that
|
|
927
|
+
* arrived without their definitions, and as `.twm-collapsible-content.visible`:
|
|
928
|
+
* an extraction that split a rule from the rule it depended on.
|
|
929
|
+
*
|
|
930
|
+
* `font-size` IS the glyph size for an icon font. `width`/`height` on the
|
|
931
|
+
* button does not constrain it — it overflows instead. */
|
|
932
|
+
.twm-panel-toggle-btn .material-symbols-outlined {
|
|
933
|
+
font-size: var(--font-size-xl);
|
|
934
|
+
line-height: 1;
|
|
935
|
+
}
|
|
893
936
|
.twm-panel-toggle-btn:hover {
|
|
894
937
|
background-color: #3f4040;
|
|
895
938
|
}
|
|
@@ -1139,6 +1182,13 @@ body.twm-dt-col-resizing {
|
|
|
1139
1182
|
.twm-collapsible-content {
|
|
1140
1183
|
display: none;
|
|
1141
1184
|
}
|
|
1185
|
+
/* C8. The rule `toggleCategory()` has always assumed and no stylesheet has ever
|
|
1186
|
+
* provided. Without it the collapsible primitive is non-functional as shipped:
|
|
1187
|
+
* the toggle adds `.visible` and nothing acts on the class, so a category that
|
|
1188
|
+
* "opens" stays `display: none`. One rule; it was simply missing. */
|
|
1189
|
+
.twm-collapsible-content.visible {
|
|
1190
|
+
display: block;
|
|
1191
|
+
}
|
|
1142
1192
|
/* ============================================
|
|
1143
1193
|
Autocomplete Field Component (legacy-variable style)
|
|
1144
1194
|
============================================ */
|
|
@@ -1259,6 +1309,27 @@ body.twm-dt-col-resizing {
|
|
|
1259
1309
|
display: none;
|
|
1260
1310
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
1261
1311
|
}
|
|
1312
|
+
/* `#open()` adds `visible` and `#close()` takes it off again
|
|
1313
|
+
(`ui/components/autocomplete_field.js`), and until now NEITHER stylesheet
|
|
1314
|
+
said what the class meant — while the dropdown above is `display: none`. So
|
|
1315
|
+
the suggestion list has never been shown to anybody, in any consumer, for as
|
|
1316
|
+
long as the component has existed: the provider ran, the items rendered, and
|
|
1317
|
+
the element stayed hidden. This is the same shape as
|
|
1318
|
+
`.twm-action-dropdown-menu.visible`, which is the pattern the component was
|
|
1319
|
+
written against. */
|
|
1320
|
+
.twm-autocomplete-field__dropdown.visible {
|
|
1321
|
+
display: block;
|
|
1322
|
+
}
|
|
1323
|
+
/* `#renderDropdown()` marks the keyboard cursor's row `active` and
|
|
1324
|
+
`#moveSelection()` moves it — with no rule to draw it. Arrowing down the list
|
|
1325
|
+
therefore moved something invisible, and Enter picked a row the user had no
|
|
1326
|
+
way to see. `:hover` above is the mouse's answer to the same question; this
|
|
1327
|
+
is the keyboard's, and it is deliberately the stronger of the two so that
|
|
1328
|
+
moving the pointer across the list does not hide where the cursor is. */
|
|
1329
|
+
.twm-autocomplete-field__item.active {
|
|
1330
|
+
background-color: var(--state-selected, rgba(0, 122, 204, 0.15));
|
|
1331
|
+
box-shadow: inset 2px 0 0 var(--accent-bright, #007acc);
|
|
1332
|
+
}
|
|
1262
1333
|
.twm-autocomplete-field__item {
|
|
1263
1334
|
display: flex;
|
|
1264
1335
|
align-items: center;
|
|
@@ -1356,7 +1427,17 @@ body.twm-dt-col-resizing {
|
|
|
1356
1427
|
padding: var(--space-sm) 0;
|
|
1357
1428
|
backdrop-filter: saturate(120%) blur(2px);
|
|
1358
1429
|
}
|
|
1430
|
+
/* A `<button role="menuitem">`, not a div — that is what makes the menu
|
|
1431
|
+
* operable by keyboard without re-implementing Enter, Space and focus. The
|
|
1432
|
+
* first five declarations are the button reset that buys it: without them the
|
|
1433
|
+
* UA paints its own background, border and centred text over everything below. */
|
|
1359
1434
|
.twm-context-menu-item {
|
|
1435
|
+
appearance: none;
|
|
1436
|
+
background: none;
|
|
1437
|
+
border: 0;
|
|
1438
|
+
width: 100%;
|
|
1439
|
+
text-align: left;
|
|
1440
|
+
color: inherit;
|
|
1360
1441
|
padding: var(--space-md) 14px;
|
|
1361
1442
|
cursor: pointer;
|
|
1362
1443
|
font-family: Segoe UI, Arial, sans-serif;
|
|
@@ -1366,9 +1447,25 @@ body.twm-dt-col-resizing {
|
|
|
1366
1447
|
align-items: center;
|
|
1367
1448
|
gap: 10px;
|
|
1368
1449
|
}
|
|
1369
|
-
.twm-context-menu-item:hover
|
|
1450
|
+
.twm-context-menu-item:hover:not(.disabled),
|
|
1451
|
+
.twm-context-menu-item:focus-visible {
|
|
1370
1452
|
background: #2a2a2a;
|
|
1453
|
+
outline: none;
|
|
1454
|
+
}
|
|
1455
|
+
/* A DISABLED ITEM MUST LOOK DISABLED.
|
|
1456
|
+
*
|
|
1457
|
+
* `showContextMenu` has always added `.disabled` and bound no click listener to
|
|
1458
|
+
* it — the behaviour was right and there was no rule for the class anywhere, so
|
|
1459
|
+
* a disabled item rendered at full opacity, kept `cursor: pointer`, still
|
|
1460
|
+
* highlighted on hover, and did nothing when clicked. That is the exact shape of
|
|
1461
|
+
* the `.twm-collapsible-content.visible` bug: a class the JS sets, a stylesheet
|
|
1462
|
+
* that has never heard of it, and a feature that looks live and is not. */
|
|
1463
|
+
.twm-context-menu-item.disabled {
|
|
1464
|
+
opacity: 0.4;
|
|
1465
|
+
cursor: default;
|
|
1466
|
+
color: var(--text-muted);
|
|
1371
1467
|
}
|
|
1468
|
+
.twm-context-menu-item.disabled.twm-delete-node { color: var(--text-muted); }
|
|
1372
1469
|
.twm-context-menu-item.twm-delete-node {
|
|
1373
1470
|
color: #ff6b6b;
|
|
1374
1471
|
}
|
|
@@ -2338,20 +2435,536 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
2338
2435
|
border-radius: var(--radius-sm);
|
|
2339
2436
|
padding: var(--space-xs) 12px;
|
|
2340
2437
|
font-size: var(--font-size-sm);
|
|
2341
|
-
cursor: pointer;
|
|
2342
|
-
transition: border-color var(--transition-normal), color var(--transition-normal);
|
|
2343
|
-
}
|
|
2344
|
-
.twm-run-progress__stop-btn:hover:not(:disabled) {
|
|
2345
|
-
border-color: #f44336;
|
|
2346
|
-
color: #f44336;
|
|
2438
|
+
cursor: pointer;
|
|
2439
|
+
transition: border-color var(--transition-normal), color var(--transition-normal);
|
|
2440
|
+
}
|
|
2441
|
+
.twm-run-progress__stop-btn:hover:not(:disabled) {
|
|
2442
|
+
border-color: #f44336;
|
|
2443
|
+
color: #f44336;
|
|
2444
|
+
}
|
|
2445
|
+
.twm-run-progress__stop-btn:disabled {
|
|
2446
|
+
opacity: 0.5;
|
|
2447
|
+
cursor: default;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
/* ═══════════════════════════════════════════════════════════════════════
|
|
2451
|
+
@flexdesk/tiles — THE TILE GRID'S CHROME
|
|
2452
|
+
|
|
2453
|
+
THIS STYLESHEET HAD NEVER EXISTED. `src/tiles/` was extracted from
|
|
2454
|
+
EcoAgent's `ui/js/results/` with its JavaScript and without its CSS, so
|
|
2455
|
+
every consumer of `@flexdesk/tiles` got a `TileGrid` that ran, dragged,
|
|
2456
|
+
resized, saved layouts — and drew a column of unstyled `<div>`s.
|
|
2457
|
+
`TileGrid._init()` sets `--grid-columns`, `--grid-row-height` and
|
|
2458
|
+
`--grid-gap` on the grid element (`src/tiles/tile_grid.js`), and until
|
|
2459
|
+
these rules landed NOTHING IN ANY STYLESHEET READ THEM: no
|
|
2460
|
+
`display: grid`, no `grid-template-columns`, no `grid-auto-rows`. That is
|
|
2461
|
+
the CSS-side half of this repository's signature bug — not a class the JS
|
|
2462
|
+
sets and no rule defines, but the reverse, three custom properties written
|
|
2463
|
+
by the JS into a void.
|
|
2464
|
+
|
|
2465
|
+
THE CLASS NAMES ARE UNPREFIXED, deliberately and unusually. Everything
|
|
2466
|
+
else in this file is `twm-`; these are not, because the names are compiled
|
|
2467
|
+
into the JavaScript that was extracted (`className = 'tile-grid'`,
|
|
2468
|
+
`'tile-header'`, `classList.add('dragging')` …) and renaming them upstream
|
|
2469
|
+
would be a breaking change to every consumer for a cosmetic gain. The one
|
|
2470
|
+
place the prefix DOES appear — `.twm-tile-error`, below — is the exception
|
|
2471
|
+
that proves it: `showError()` emits the prefixed spelling, so the donor's
|
|
2472
|
+
`.tile-error` rule is NOT ported here. Porting it would have written a
|
|
2473
|
+
dead rule directly beside a live class, which is the mistake this comment
|
|
2474
|
+
exists to stop the next reader from repeating.
|
|
2475
|
+
|
|
2476
|
+
Values come from `tokens.css`, so swapping the tokens restyles the tiles
|
|
2477
|
+
with everything else. Two colour literals survive the port and each says at
|
|
2478
|
+
its own rule why: a light accent, for which there is no token (the nearest,
|
|
2479
|
+
`--accent-bright`, is a border colour on this background rather than a text
|
|
2480
|
+
one), and the error red already shipped in `.twm-tile-error`.
|
|
2481
|
+
Ported from EcoAgent `ui/css/results_widgets.css` (the donor still owns its
|
|
2482
|
+
own copy — it does not load this file).
|
|
2483
|
+
═══════════════════════════════════════════════════════════════════════ */
|
|
2484
|
+
|
|
2485
|
+
/* ── the grid container ──────────────────────────────────────────────── */
|
|
2486
|
+
/* The three custom properties are set by `TileGrid._init()` as inline style
|
|
2487
|
+
on this element; the fallbacks are the constructor's defaults, so a grid
|
|
2488
|
+
element built by hand still lays out. */
|
|
2489
|
+
.tile-grid {
|
|
2490
|
+
display: grid;
|
|
2491
|
+
grid-template-columns: repeat(var(--grid-columns, 12), 1fr);
|
|
2492
|
+
grid-auto-rows: var(--grid-row-height, 80px);
|
|
2493
|
+
gap: var(--grid-gap, 16px);
|
|
2494
|
+
padding: var(--grid-gap, 16px) 0 0 0;
|
|
2495
|
+
/* A floor rather than a height: the rows are what size the grid. It
|
|
2496
|
+
matters only when the board is empty, and an empty board that is zero
|
|
2497
|
+
pixels tall is a board you cannot drop the first tile onto. */
|
|
2498
|
+
min-height: 400px;
|
|
2499
|
+
position: relative;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
/* ── the built-in toolbar ────────────────────────────────────────────── */
|
|
2503
|
+
/* `_buildToolbar()` emits `tile-grid-toolbar tile-grid-toolbar--bottom` and no
|
|
2504
|
+
other variant, so the divider lives on the modifier: a rule that put a
|
|
2505
|
+
`border-bottom` on the base class — as the donor's did, when the toolbar sat
|
|
2506
|
+
above the grid — draws a line along the wrong edge here. A consumer that
|
|
2507
|
+
passes `showToolbar: false` and builds its own toolbar gets none of this. */
|
|
2508
|
+
.tile-grid-toolbar {
|
|
2509
|
+
display: flex;
|
|
2510
|
+
justify-content: space-between;
|
|
2511
|
+
align-items: center;
|
|
2512
|
+
gap: var(--space-md);
|
|
2513
|
+
padding: var(--space-lg) var(--space-xl);
|
|
2514
|
+
background: rgba(255, 255, 255, 0.02);
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
.tile-grid-toolbar--bottom {
|
|
2518
|
+
border-top: 1px solid var(--border-subtle);
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
/* `toolbar-group`, `toolbar-btn`, `btn-text` and `dropdown-arrow` are names
|
|
2522
|
+
generic enough that a consumer almost certainly has its own. They are
|
|
2523
|
+
scoped under the toolbar rather than claimed globally — a library that
|
|
2524
|
+
defines a bare `.toolbar-btn` restyles buttons it has never met. */
|
|
2525
|
+
.tile-grid-toolbar .toolbar-group {
|
|
2526
|
+
display: flex;
|
|
2527
|
+
align-items: center;
|
|
2528
|
+
gap: var(--space-md);
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
.tile-grid-toolbar .add-widget-container {
|
|
2532
|
+
position: relative;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
.tile-grid-toolbar .toolbar-btn {
|
|
2536
|
+
display: inline-flex;
|
|
2537
|
+
align-items: center;
|
|
2538
|
+
gap: var(--space-xs);
|
|
2539
|
+
padding: var(--space-xs) var(--space-lg);
|
|
2540
|
+
background: rgba(255, 255, 255, 0.04);
|
|
2541
|
+
border: 1px solid var(--border-default);
|
|
2542
|
+
border-radius: var(--radius-sm);
|
|
2543
|
+
color: var(--text-body);
|
|
2544
|
+
font-family: inherit;
|
|
2545
|
+
font-size: var(--font-size-sm);
|
|
2546
|
+
cursor: pointer;
|
|
2547
|
+
transition: background var(--transition-normal), border-color var(--transition-normal);
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
.tile-grid-toolbar .toolbar-btn:hover {
|
|
2551
|
+
background: var(--state-hover);
|
|
2552
|
+
border-color: var(--border-strong);
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
.tile-grid-toolbar .toolbar-btn .material-symbols-outlined {
|
|
2556
|
+
font-size: var(--font-size-xl);
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
.tile-grid-toolbar .btn-text {
|
|
2560
|
+
white-space: nowrap;
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
.tile-grid-toolbar .dropdown-arrow {
|
|
2564
|
+
opacity: 0.6;
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
/* `--main` is the ONLY group `_buildToolbar()` emits, and all three buttons
|
|
2568
|
+
sit inside it, so it takes the strip's whole width — `space-between` on the
|
|
2569
|
+
toolbar has nothing to space until a consumer adds a second group. */
|
|
2570
|
+
.tile-grid-toolbar .toolbar-group--main {
|
|
2571
|
+
flex: 1;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
/* Add Widget is the toolbar's one primary verb, so it is the one that carries
|
|
2575
|
+
the accent. */
|
|
2576
|
+
.add-widget-btn {
|
|
2577
|
+
background: rgba(0, 122, 204, 0.15);
|
|
2578
|
+
border-color: rgba(0, 122, 204, 0.3);
|
|
2579
|
+
color: var(--text-bright);
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
.add-widget-btn:hover {
|
|
2583
|
+
background: rgba(0, 122, 204, 0.25);
|
|
2584
|
+
border-color: rgba(0, 122, 204, 0.5);
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
/* The other two are secondary — Export is an aside and Reset Layout throws the
|
|
2588
|
+
board away, and neither should read as the thing to press. */
|
|
2589
|
+
.export-btn,
|
|
2590
|
+
.reset-layout-btn {
|
|
2591
|
+
color: var(--text-muted);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
.export-btn:hover,
|
|
2595
|
+
.reset-layout-btn:hover {
|
|
2596
|
+
color: var(--text-body);
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
/* ── the tile ────────────────────────────────────────────────────────── */
|
|
2600
|
+
/* Flat, one 1 px border, no radius and no shadow: dense expert-tool chrome,
|
|
2601
|
+
the register of a trading terminal rather than a card wall. The donor also
|
|
2602
|
+
carried `margin: -1px 0 0 -1px` so that adjacent borders collapsed into one
|
|
2603
|
+
line — which is only true at `gap: 0`, and this grid's default gap is 16 px,
|
|
2604
|
+
where the same rule just shifts every tile a pixel up and left. Dropped on
|
|
2605
|
+
purpose; a consumer running gap-0 can add it back in one rule. */
|
|
2606
|
+
.tile {
|
|
2607
|
+
background: var(--surface-1);
|
|
2608
|
+
border: 1px solid var(--border-default);
|
|
2609
|
+
border-radius: 0;
|
|
2610
|
+
display: flex;
|
|
2611
|
+
flex-direction: column;
|
|
2612
|
+
position: relative;
|
|
2613
|
+
overflow: hidden;
|
|
2614
|
+
height: 100%;
|
|
2615
|
+
min-height: 0;
|
|
2616
|
+
transition: border-color var(--transition-normal);
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
.tile:hover {
|
|
2620
|
+
border-color: var(--border-strong);
|
|
2621
|
+
box-shadow: none;
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
.tile.dragging {
|
|
2625
|
+
opacity: 0.5;
|
|
2626
|
+
z-index: var(--z-dropdown);
|
|
2627
|
+
box-shadow: var(--shadow-lg);
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
.tile.resizing {
|
|
2631
|
+
z-index: var(--z-dropdown);
|
|
2632
|
+
border-color: var(--accent-bright);
|
|
2633
|
+
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.3);
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
/* Selected — set by `_onConfigClick()` while the config dialog is open, and
|
|
2637
|
+
cleared from every sibling by the same handler. #78b9ff is a literal
|
|
2638
|
+
because there is no light-accent token: `--accent-bright` is #007acc, which
|
|
2639
|
+
is a border colour on this background and not a text one. */
|
|
2640
|
+
.tile.tile--selected {
|
|
2641
|
+
border-color: #78b9ff;
|
|
2642
|
+
box-shadow: 0 0 0 2px rgba(120, 185, 255, 0.3), var(--shadow-md);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
.tile.tile--selected .tile-header {
|
|
2646
|
+
background: rgba(120, 185, 255, 0.1);
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
.tile.tile--selected .tile-title {
|
|
2650
|
+
color: #78b9ff;
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
/* Read-only. `_buildChrome()`'s read-only branch emits no drag handle, no
|
|
2654
|
+
cogwheel, no ⋯ menu, no remove button and no resize handle, so there is
|
|
2655
|
+
nothing here to hide — what is left is to stop the tile advertising the
|
|
2656
|
+
affordances it no longer has. */
|
|
2657
|
+
.tile--readonly .tile-header {
|
|
2658
|
+
cursor: default;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
.tile--readonly:hover {
|
|
2662
|
+
border-color: var(--border-default);
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
/* ── the header strip ────────────────────────────────────────────────── */
|
|
2666
|
+
.tile-header {
|
|
2667
|
+
display: flex;
|
|
2668
|
+
align-items: center;
|
|
2669
|
+
gap: var(--space-xs);
|
|
2670
|
+
padding: 0 var(--space-sm);
|
|
2671
|
+
background: var(--surface-0);
|
|
2672
|
+
border-bottom: 1px solid var(--border-default);
|
|
2673
|
+
min-height: 22px;
|
|
2674
|
+
flex-shrink: 0;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
.tile-drag-handle {
|
|
2678
|
+
display: flex;
|
|
2679
|
+
align-items: center;
|
|
2680
|
+
justify-content: center;
|
|
2681
|
+
width: 16px;
|
|
2682
|
+
height: 18px;
|
|
2683
|
+
color: rgba(255, 255, 255, 0.3);
|
|
2684
|
+
cursor: grab;
|
|
2685
|
+
transition: color var(--transition-normal);
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
.tile-drag-handle:hover {
|
|
2689
|
+
color: rgba(255, 255, 255, 0.6);
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
.tile-drag-handle:active {
|
|
2693
|
+
cursor: grabbing;
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
.tile-drag-handle .material-symbols-outlined {
|
|
2697
|
+
font-size: var(--font-size-lg);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
/* `cursor: default`, where the donor said `pointer`. In EcoAgent the title
|
|
2701
|
+
opened the config panel; in this library that moved to the cogwheel alone
|
|
2702
|
+
("Config opens ONLY via the explicit cogwheel button" — `_buildChrome`), so
|
|
2703
|
+
a pointer cursor here promises a click that does nothing. */
|
|
2704
|
+
.tile-title {
|
|
2705
|
+
flex: 1;
|
|
2706
|
+
min-width: 0;
|
|
2707
|
+
font-size: var(--font-size-xs);
|
|
2708
|
+
font-weight: var(--font-weight-semibold);
|
|
2709
|
+
color: var(--text-body);
|
|
2710
|
+
text-transform: uppercase;
|
|
2711
|
+
letter-spacing: 0.04em;
|
|
2712
|
+
white-space: nowrap;
|
|
2713
|
+
overflow: hidden;
|
|
2714
|
+
text-overflow: ellipsis;
|
|
2715
|
+
cursor: default;
|
|
2716
|
+
padding: 0;
|
|
2717
|
+
border-radius: 0;
|
|
2718
|
+
line-height: 1;
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
.tile-controls {
|
|
2722
|
+
display: flex;
|
|
2723
|
+
align-items: center;
|
|
2724
|
+
gap: var(--space-xs);
|
|
2725
|
+
margin-left: var(--space-md);
|
|
2726
|
+
flex-shrink: 0;
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
/* One geometry for every icon button in the strip. They differ only in what
|
|
2730
|
+
they turn when you hover them, which is the next block. */
|
|
2731
|
+
.tile-config-btn,
|
|
2732
|
+
.tile-menu-btn,
|
|
2733
|
+
.tile-expand-btn,
|
|
2734
|
+
.tile-remove-btn {
|
|
2735
|
+
display: flex;
|
|
2736
|
+
align-items: center;
|
|
2737
|
+
justify-content: center;
|
|
2738
|
+
width: 20px;
|
|
2739
|
+
height: 20px;
|
|
2740
|
+
padding: 0;
|
|
2741
|
+
background: transparent;
|
|
2742
|
+
border: none;
|
|
2743
|
+
border-radius: var(--radius-sm);
|
|
2744
|
+
color: rgba(255, 255, 255, 0.4);
|
|
2745
|
+
cursor: pointer;
|
|
2746
|
+
transition: background var(--transition-normal), color var(--transition-normal);
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
.tile-config-btn:hover,
|
|
2750
|
+
.tile-menu-btn:hover,
|
|
2751
|
+
.tile-expand-btn:hover {
|
|
2752
|
+
background: var(--state-hover);
|
|
2753
|
+
color: #78b9ff;
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
.tile-remove-btn:hover {
|
|
2757
|
+
background: var(--state-hover);
|
|
2758
|
+
color: #ff6b6b;
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
.tile-config-btn .material-symbols-outlined,
|
|
2762
|
+
.tile-menu-btn .material-symbols-outlined,
|
|
2763
|
+
.tile-expand-btn .material-symbols-outlined,
|
|
2764
|
+
.tile-remove-btn .material-symbols-outlined {
|
|
2765
|
+
font-size: var(--font-size-xl);
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
/* The ⓘ button carries its own inline `style` from `_buildChrome`, and inline
|
|
2769
|
+
style beats any rule in this file — background, border, padding and colour
|
|
2770
|
+
are already decided there and CANNOT be changed from here. So this rule adds
|
|
2771
|
+
only what the inline style leaves open. A hover background written here
|
|
2772
|
+
would be a rule that never applies: exactly the dead rule the header comment
|
|
2773
|
+
warns about, arrived at from the other direction. */
|
|
2774
|
+
.tile-info-btn {
|
|
2775
|
+
width: 20px;
|
|
2776
|
+
height: 20px;
|
|
2777
|
+
border-radius: var(--radius-sm);
|
|
2778
|
+
flex-shrink: 0;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
/* ── the ⋯ menu ──────────────────────────────────────────────────────── */
|
|
2782
|
+
/* Rendered from `TileBase.menuItems()`; an empty list emits no wrapper at all,
|
|
2783
|
+
so none of this draws for a consumer that overrides the hook to `[]`. */
|
|
2784
|
+
.tile-menu-wrapper {
|
|
2785
|
+
position: relative;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
.tile-menu-dropdown {
|
|
2789
|
+
position: absolute;
|
|
2790
|
+
top: 100%;
|
|
2791
|
+
right: 0;
|
|
2792
|
+
z-index: var(--z-dropdown);
|
|
2793
|
+
min-width: 180px;
|
|
2794
|
+
padding: var(--space-xs) 0;
|
|
2795
|
+
background: var(--surface-2);
|
|
2796
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
2797
|
+
border-radius: var(--radius-md);
|
|
2798
|
+
box-shadow: var(--shadow-md);
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
/* `!important`, matching `.twm-action-dropdown-menu[hidden]` below and for the
|
|
2802
|
+
same reason: `hidden` is the JS's only way of closing this menu
|
|
2803
|
+
(`menuDropdown.hidden = !menuDropdown.hidden`), and the UA implements the
|
|
2804
|
+
attribute as the weakest possible rule — any consumer selector that sets a
|
|
2805
|
+
display on the dropdown would leave it open with nothing to close it. */
|
|
2806
|
+
.tile-menu-dropdown[hidden] {
|
|
2807
|
+
display: none !important;
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
.tile-menu-item {
|
|
2811
|
+
display: flex;
|
|
2812
|
+
align-items: center;
|
|
2813
|
+
gap: var(--space-md);
|
|
2814
|
+
width: 100%;
|
|
2815
|
+
padding: var(--space-sm) var(--space-lg);
|
|
2816
|
+
background: none;
|
|
2817
|
+
border: none;
|
|
2818
|
+
color: var(--text-secondary);
|
|
2819
|
+
font-family: inherit;
|
|
2820
|
+
font-size: var(--font-size-sm);
|
|
2821
|
+
text-align: left;
|
|
2822
|
+
cursor: pointer;
|
|
2823
|
+
white-space: nowrap;
|
|
2824
|
+
transition: background var(--transition-fast);
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
.tile-menu-item:hover {
|
|
2828
|
+
background: var(--state-active);
|
|
2829
|
+
color: var(--text-bright);
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
.tile-menu-item .material-symbols-outlined {
|
|
2833
|
+
font-size: var(--font-size-xl);
|
|
2834
|
+
color: rgba(255, 255, 255, 0.5);
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
.tile-menu-item:hover .material-symbols-outlined {
|
|
2838
|
+
color: #78b9ff;
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
/* ── the content area ────────────────────────────────────────────────── */
|
|
2842
|
+
/* `min-height: 0` is the load-bearing declaration, not the padding: a flex
|
|
2843
|
+
child's default `min-height: auto` refuses to shrink below its content, so
|
|
2844
|
+
without it a tall table or a chart pushes the tile past its grid rows
|
|
2845
|
+
instead of scrolling inside them. */
|
|
2846
|
+
.tile-content {
|
|
2847
|
+
flex: 1;
|
|
2848
|
+
padding: var(--space-lg);
|
|
2849
|
+
overflow: hidden;
|
|
2850
|
+
position: relative;
|
|
2851
|
+
min-height: 0;
|
|
2852
|
+
display: flex;
|
|
2853
|
+
flex-direction: column;
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
/* A chart library that measures its container on draw gets a container with a
|
|
2857
|
+
real size. The `!important` is the only lever there is: Plotly writes width
|
|
2858
|
+
and height INLINE onto `.js-plotly-plot` when it draws, an inline
|
|
2859
|
+
declaration outranks any selector, and `!important` is what outranks it —
|
|
2860
|
+
without which a chart keeps the size it had when the tile was half as wide. */
|
|
2861
|
+
.tile-content > [class*="-container"] {
|
|
2862
|
+
flex: 1;
|
|
2863
|
+
min-height: 0;
|
|
2864
|
+
width: 100%;
|
|
2865
|
+
position: relative;
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
.tile-content .js-plotly-plot,
|
|
2869
|
+
.tile-content .plot-container.plotly,
|
|
2870
|
+
.tile-content .svg-container {
|
|
2871
|
+
width: 100% !important;
|
|
2872
|
+
height: 100% !important;
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
/* ── resize handle and drag ghost ────────────────────────────────────── */
|
|
2876
|
+
.tile-resize-handle {
|
|
2877
|
+
position: absolute;
|
|
2878
|
+
background: transparent;
|
|
2879
|
+
z-index: var(--z-sticky);
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
.tile-resize-handle.se {
|
|
2883
|
+
bottom: 0;
|
|
2884
|
+
right: 0;
|
|
2885
|
+
width: 24px;
|
|
2886
|
+
height: 24px;
|
|
2887
|
+
cursor: se-resize;
|
|
2888
|
+
display: flex;
|
|
2889
|
+
align-items: center;
|
|
2890
|
+
justify-content: center;
|
|
2891
|
+
color: rgba(255, 255, 255, 0.2);
|
|
2892
|
+
transition: color var(--transition-normal);
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
.tile-resize-handle.se:hover {
|
|
2896
|
+
color: rgba(255, 255, 255, 0.5);
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
.tile-resize-handle .material-symbols-outlined {
|
|
2900
|
+
font-size: var(--font-size-xl);
|
|
2901
|
+
transform: rotate(-45deg);
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
/* The drop preview, positioned by `_updateGhost()` in grid coordinates. */
|
|
2905
|
+
.tile-ghost {
|
|
2906
|
+
position: absolute;
|
|
2907
|
+
background: rgba(0, 122, 204, 0.15);
|
|
2908
|
+
border: 2px dashed rgba(0, 122, 204, 0.5);
|
|
2909
|
+
border-radius: var(--radius-sm);
|
|
2910
|
+
pointer-events: none;
|
|
2911
|
+
z-index: 50;
|
|
2912
|
+
transition: transform 0.1s ease;
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
/* ── the three content states ────────────────────────────────────────── */
|
|
2916
|
+
/* `showLoading()`, `showEmpty()` and `showError()` each replace the whole of
|
|
2917
|
+
`.tile-content`, so all three centre themselves in it. */
|
|
2918
|
+
.tile-loading {
|
|
2919
|
+
display: flex;
|
|
2920
|
+
flex-direction: column;
|
|
2921
|
+
align-items: center;
|
|
2922
|
+
justify-content: center;
|
|
2923
|
+
height: 100%;
|
|
2924
|
+
gap: var(--space-md);
|
|
2925
|
+
color: rgba(255, 255, 255, 0.5);
|
|
2926
|
+
font-size: var(--font-size-sm);
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
.tile-loading .material-symbols-outlined {
|
|
2930
|
+
font-size: 24px;
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
/* Scoped, because `.spinning` on its own is a name a consumer is entitled to
|
|
2934
|
+
use for its own spinner. `spin` is not scopeable — `@keyframes` names are
|
|
2935
|
+
global — and a consumer that defines its own `spin` later in the cascade
|
|
2936
|
+
simply spins this icon its way, which is harmless. */
|
|
2937
|
+
.tile-loading .spinning {
|
|
2938
|
+
animation: spin 1s linear infinite;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
@keyframes spin {
|
|
2942
|
+
from { transform: rotate(0deg); }
|
|
2943
|
+
to { transform: rotate(360deg); }
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
.tile-empty {
|
|
2947
|
+
display: flex;
|
|
2948
|
+
flex-direction: column;
|
|
2949
|
+
align-items: center;
|
|
2950
|
+
justify-content: center;
|
|
2951
|
+
height: 100%;
|
|
2952
|
+
gap: var(--space-md);
|
|
2953
|
+
color: rgba(255, 255, 255, 0.4);
|
|
2954
|
+
font-size: var(--font-size-sm);
|
|
2955
|
+
text-align: center;
|
|
2347
2956
|
}
|
|
2348
|
-
|
|
2957
|
+
|
|
2958
|
+
.tile-empty .material-symbols-outlined {
|
|
2959
|
+
font-size: 32px;
|
|
2349
2960
|
opacity: 0.5;
|
|
2350
|
-
cursor: default;
|
|
2351
2961
|
}
|
|
2352
2962
|
|
|
2353
|
-
/*
|
|
2354
|
-
|
|
2963
|
+
/* THE ERROR STATE IS PREFIXED AND THE OTHER TWO ARE NOT. `showError()` emits
|
|
2964
|
+
`.twm-tile-error` where `showEmpty()` emits `.tile-empty` — an asymmetry
|
|
2965
|
+
that is now a fixed fact of the wire, because this rule shipped before the
|
|
2966
|
+
rest of the block did. The donor's unprefixed `.tile-error` is deliberately
|
|
2967
|
+
NOT ported: it would sit here looking correct and matching nothing. */
|
|
2355
2968
|
.twm-tile-error {
|
|
2356
2969
|
display: flex;
|
|
2357
2970
|
flex-direction: column;
|
|
@@ -2361,6 +2974,11 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
2361
2974
|
gap: var(--space-md);
|
|
2362
2975
|
color: #ff6b6b;
|
|
2363
2976
|
font-size: var(--font-size-sm);
|
|
2977
|
+
text-align: center;
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
.twm-tile-error .material-symbols-outlined {
|
|
2981
|
+
font-size: 32px;
|
|
2364
2982
|
}
|
|
2365
2983
|
/* ===================================================================
|
|
2366
2984
|
REUSABLE ACTION DROPDOWN COMPONENT
|
|
@@ -2384,6 +3002,29 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
2384
3002
|
.twm-action-dropdown-menu[hidden] {
|
|
2385
3003
|
display: none !important;
|
|
2386
3004
|
}
|
|
3005
|
+
/* THE OPEN STATE. `ActionDropdown.open()` un-hides the menu and then adds
|
|
3006
|
+
`.visible` on the next frame to fade it in (action_dropdown.js:172-180) —
|
|
3007
|
+
and no rule for that class existed, so the menu unhid itself into
|
|
3008
|
+
`opacity: 0; pointer-events: none` and stayed there. Every ActionDropdown in
|
|
3009
|
+
the library, "Add Widget" included, opened invisible and un-clickable.
|
|
3010
|
+
The same defect `.twm-collapsible-content.visible` had, in the same shape:
|
|
3011
|
+
the JS toggles a class the stylesheet never grew. */
|
|
3012
|
+
.twm-action-dropdown-menu.visible {
|
|
3013
|
+
opacity: 1;
|
|
3014
|
+
pointer-events: auto;
|
|
3015
|
+
}
|
|
3016
|
+
/* The option's text column. `_renderOptions` wraps the label and the
|
|
3017
|
+
description in `<span class="option-text">` (action_dropdown.js:125-128) and
|
|
3018
|
+
both children are inline, so a described option rendered as
|
|
3019
|
+
"Add Widget Put a widget on this desktop" on one run-together line. The
|
|
3020
|
+
description is a SECOND LINE by construction; this is what makes it one.
|
|
3021
|
+
`min-width: 0` so the ellipsis on the two children has something to bite on
|
|
3022
|
+
inside the flex row. */
|
|
3023
|
+
.twm-action-dropdown-option .option-text {
|
|
3024
|
+
display: flex;
|
|
3025
|
+
flex-direction: column;
|
|
3026
|
+
min-width: 0;
|
|
3027
|
+
}
|
|
2387
3028
|
/* Dropdown options */
|
|
2388
3029
|
.twm-action-dropdown-option {
|
|
2389
3030
|
display: flex;
|
|
@@ -2556,6 +3197,98 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
2556
3197
|
/* Enable transitions for minimize/maximize animations */
|
|
2557
3198
|
transition: transform 0.2s ease-out, opacity 0.2s ease-out, box-shadow var(--transition-normal);
|
|
2558
3199
|
}
|
|
3200
|
+
/* C3. Containment. `--contained` swaps `position: fixed` for `absolute` so the
|
|
3201
|
+
* window is placed relative to its container, which must be a containing block
|
|
3202
|
+
* established with `position: relative`/`absolute` and NOTHING ELSE.
|
|
3203
|
+
*
|
|
3204
|
+
* This is the most dangerous rule in the containment work. `transform`,
|
|
3205
|
+
* `filter`, `contain` and `will-change` on the container ALSO create a
|
|
3206
|
+
* containing block — and they additionally capture `position: fixed`
|
|
3207
|
+
* descendants. DataTable's filter dropdown (`position: fixed; z-index: 10001`)
|
|
3208
|
+
* and the autocomplete dropdown (`z-index: 10000000`) deliberately escape their
|
|
3209
|
+
* tile to the viewport; under a transformed ancestor they become
|
|
3210
|
+
* container-relative and are clipped by the container's `overflow: hidden`.
|
|
3211
|
+
* The symptom is "the filter dropdown is cut in half" and the cause is three
|
|
3212
|
+
* files away. */
|
|
3213
|
+
/* C11. Aero snap — the preview shown while a drag is over an edge zone.
|
|
3214
|
+
* Absolutely positioned in the SAME coordinate space the window uses, so it is
|
|
3215
|
+
* the container when contained and the page otherwise. Below the window's own
|
|
3216
|
+
* z-index and above everything else in the container, so it reads as a target
|
|
3217
|
+
* behind the thing being dragged. */
|
|
3218
|
+
.twm-snap-preview {
|
|
3219
|
+
position: absolute;
|
|
3220
|
+
z-index: 5999;
|
|
3221
|
+
pointer-events: none;
|
|
3222
|
+
background: rgba(23, 119, 179, 0.18);
|
|
3223
|
+
border: 1px solid var(--accent-bright);
|
|
3224
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
|
3225
|
+
transition: left 0.09s ease, top 0.09s ease, width 0.09s ease, height 0.09s ease;
|
|
3226
|
+
}
|
|
3227
|
+
/* C15. A CONTROLLER'S preview is positioned in VIEWPORT pixels, because its
|
|
3228
|
+
* rectangle came from hit-testing somebody else's DOM (`getBoundingClientRect`)
|
|
3229
|
+
* rather than from `_bounds()`. `position: fixed` is what makes those numbers
|
|
3230
|
+
* mean what they say; leaving it `absolute` under `document.body` would be
|
|
3231
|
+
* right only until the page scrolls. */
|
|
3232
|
+
.twm-snap-preview--viewport {
|
|
3233
|
+
position: fixed;
|
|
3234
|
+
}
|
|
3235
|
+
/* A window whose position came from a snap rather than from a drag. Nothing
|
|
3236
|
+
* depends on it visually; it is there so a consumer can tell the two apart. */
|
|
3237
|
+
.twm-managed-window--snapped { }
|
|
3238
|
+
/* R3. HALF TRANSPARENT THE MOMENT THE DRAG LEAVES THE TILE IT CAME FROM.
|
|
3239
|
+
*
|
|
3240
|
+
* The one signal that separates "I am moving this window around on its own
|
|
3241
|
+
* pane" from "letting go now will put this window into the tree somewhere".
|
|
3242
|
+
* Inside its origin pane the window is fully opaque and nothing is armed; the
|
|
3243
|
+
* moment the pointer crosses out of it the window goes translucent and the drop
|
|
3244
|
+
* preview starts drawing what a release would produce.
|
|
3245
|
+
*
|
|
3246
|
+
* No transition of its own: `.twm-managed-window` already transitions `opacity`
|
|
3247
|
+
* over 0.2s for the minimize animation, so the fade comes for free — and adding
|
|
3248
|
+
* a second, faster one here would make the same property animate at two speeds
|
|
3249
|
+
* depending on which class won. Translucent, not ghostly: the window is still
|
|
3250
|
+
* the thing being aimed, and at 0.5 the pane underneath reads through it while
|
|
3251
|
+
* its title bar stays legible. */
|
|
3252
|
+
.twm-managed-window--detached {
|
|
3253
|
+
opacity: 0.5;
|
|
3254
|
+
}
|
|
3255
|
+
/* A title-bar button the WM has suppressed for the window's current state — the
|
|
3256
|
+
* minimize button on a maximised window it promoted out of a tile. `!important`
|
|
3257
|
+
* because `button.twm-managed-window__btn` sets `display: flex` and an author
|
|
3258
|
+
* declaration of `display` defeats the `hidden` attribute outright; a class
|
|
3259
|
+
* that loses the specificity tie is a control that stays on screen. */
|
|
3260
|
+
.twm-managed-window__btn--suppressed { display: none !important; }
|
|
3261
|
+
|
|
3262
|
+
.twm-managed-window--contained {
|
|
3263
|
+
position: absolute;
|
|
3264
|
+
}
|
|
3265
|
+
.twm-managed-window__backdrop--contained {
|
|
3266
|
+
position: absolute;
|
|
3267
|
+
top: 0;
|
|
3268
|
+
left: 0;
|
|
3269
|
+
right: 0;
|
|
3270
|
+
bottom: 0;
|
|
3271
|
+
}
|
|
3272
|
+
/* The layer a contained window is mounted into. `position: relative` and
|
|
3273
|
+
* `overflow: hidden` ONLY — see the warning above. */
|
|
3274
|
+
.tbl-window-layer {
|
|
3275
|
+
position: absolute;
|
|
3276
|
+
inset: 0;
|
|
3277
|
+
overflow: hidden;
|
|
3278
|
+
}
|
|
3279
|
+
/* C6. An extra titlebar button (the table cogwheel), sized like the built-ins.
|
|
3280
|
+
* R7. And the maximize button, when a consumer has given it a glyph instead of
|
|
3281
|
+
* the built-in square — the WM does, because for a window it lifted out of a
|
|
3282
|
+
* tile that button means "back to tile". A Material Symbol renders at its
|
|
3283
|
+
* `font-size` and Google's own sheet says 24px, which is three times the height
|
|
3284
|
+
* of a 20px title-bar button: the rule has to be a DESCENDANT selector like
|
|
3285
|
+
* this one to out-specify it, and a class on the glyph element itself would
|
|
3286
|
+
* quietly lose. */
|
|
3287
|
+
.twm-managed-window__btn--custom .material-symbols-outlined,
|
|
3288
|
+
.twm-managed-window__btn--maximize .material-symbols-outlined {
|
|
3289
|
+
font-size: 13px;
|
|
3290
|
+
line-height: 1;
|
|
3291
|
+
}
|
|
2559
3292
|
.twm-managed-window:focus-within {
|
|
2560
3293
|
border-color: #505050;
|
|
2561
3294
|
}
|
|
@@ -2569,6 +3302,26 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
2569
3302
|
.twm-managed-window--restoring {
|
|
2570
3303
|
animation: windowRestore 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
2571
3304
|
}
|
|
3305
|
+
/* C27. THE KEYFRAMES THE RULE ABOVE HAS ALWAYS NAMED AND NOTHING EVER DEFINED.
|
|
3306
|
+
`animation: windowRestore ...` referred to an `@keyframes` block that does
|
|
3307
|
+
not exist anywhere in either stylesheet, which CSS treats as no animation at
|
|
3308
|
+
all — so the restore was an instant pop while its mirror image, the minimise,
|
|
3309
|
+
played for 200ms. The fifth instance of this codebase's signature defect: one
|
|
3310
|
+
side names a thing the other side has never heard of, nothing throws, and no
|
|
3311
|
+
check in any of the three consumers can see it. Deliberately the exact
|
|
3312
|
+
inverse of `--minimizing`, from the same three custom properties, so the two
|
|
3313
|
+
halves of the gesture cannot describe different geometry. */
|
|
3314
|
+
@keyframes windowRestore {
|
|
3315
|
+
from {
|
|
3316
|
+
transform: translate(var(--mw-target-x, 0px), var(--mw-target-y, 100vh))
|
|
3317
|
+
scale(var(--mw-target-scale, 0.1));
|
|
3318
|
+
opacity: 0;
|
|
3319
|
+
}
|
|
3320
|
+
to {
|
|
3321
|
+
transform: none;
|
|
3322
|
+
opacity: 1;
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
2572
3325
|
/* ========== Backdrop (for modal windows) ========== */
|
|
2573
3326
|
|
|
2574
3327
|
.twm-managed-window__backdrop {
|
|
@@ -2694,6 +3447,29 @@ button.twm-managed-window__btn svg rect {
|
|
|
2694
3447
|
position: absolute;
|
|
2695
3448
|
z-index: var(--z-sticky);
|
|
2696
3449
|
}
|
|
3450
|
+
/* The eight handles' geometry. Without these the elements exist, are
|
|
3451
|
+
* `position: absolute`, and have NO width, height or offset — so every window
|
|
3452
|
+
* in every consumer is unresizable and nothing says why. Edges are 6px, corners
|
|
3453
|
+
* 12px and sit above the edges, because a corner that loses to an edge makes
|
|
3454
|
+
* diagonal resize a matter of luck. */
|
|
3455
|
+
.twm-managed-window__resize--n,
|
|
3456
|
+
.twm-managed-window__resize--s { left: 0; right: 0; height: 6px; cursor: ns-resize; }
|
|
3457
|
+
.twm-managed-window__resize--n { top: -3px; }
|
|
3458
|
+
.twm-managed-window__resize--s { bottom: -3px; }
|
|
3459
|
+
.twm-managed-window__resize--e,
|
|
3460
|
+
.twm-managed-window__resize--w { top: 0; bottom: 0; width: 6px; cursor: ew-resize; }
|
|
3461
|
+
.twm-managed-window__resize--e { right: -3px; }
|
|
3462
|
+
.twm-managed-window__resize--w { left: -3px; }
|
|
3463
|
+
.twm-managed-window__resize--ne,
|
|
3464
|
+
.twm-managed-window__resize--nw,
|
|
3465
|
+
.twm-managed-window__resize--se,
|
|
3466
|
+
.twm-managed-window__resize--sw { width: 12px; height: 12px; z-index: calc(var(--z-sticky) + 1); }
|
|
3467
|
+
.twm-managed-window__resize--ne { top: -3px; right: -3px; cursor: nesw-resize; }
|
|
3468
|
+
.twm-managed-window__resize--nw { top: -3px; left: -3px; cursor: nwse-resize; }
|
|
3469
|
+
.twm-managed-window__resize--se { bottom: -3px; right: -3px; cursor: nwse-resize; }
|
|
3470
|
+
.twm-managed-window__resize--sw { bottom: -3px; left: -3px; cursor: nesw-resize; }
|
|
3471
|
+
/* A maximised or snapped window is not resized by dragging its edges. */
|
|
3472
|
+
.twm-managed-window--maximized .twm-managed-window__resize { display: none; }
|
|
2697
3473
|
/* ========== Taskbar in Bottom Bar ========== */
|
|
2698
3474
|
|
|
2699
3475
|
.twm-bar-windows {
|
|
@@ -3572,6 +4348,24 @@ button.twm-about-dialog__btn--close:focus {
|
|
|
3572
4348
|
opacity: 0.4;
|
|
3573
4349
|
cursor: not-allowed;
|
|
3574
4350
|
}
|
|
4351
|
+
|
|
4352
|
+
/* C26. A LEADING GLYPH ON AN ACTION BUTTON — `openModal({actions:[{icon}]})`.
|
|
4353
|
+
*
|
|
4354
|
+
* Material Symbols is a FONT. It renders at `font-size`, which defaults to
|
|
4355
|
+
* 24px, and the button's `min-height` does not constrain it — so without this
|
|
4356
|
+
* the glyph overflows a 22px action button and the row reads as ragged. The
|
|
4357
|
+
* declaration is written TWICE, once as a single class and once compounded
|
|
4358
|
+
* with `.material-symbols-outlined`, because a consumer that loads Google's
|
|
4359
|
+
* hosted stylesheet loads it AFTER this one: `.material-symbols-outlined
|
|
4360
|
+
* {font-size:24px}` is a single class too, so it ties on specificity and wins
|
|
4361
|
+
* on source order. The compound selector is what actually holds. */
|
|
4362
|
+
.twm-btn__glyph,
|
|
4363
|
+
.twm-btn__glyph.material-symbols-outlined {
|
|
4364
|
+
font-size: var(--font-size-xl, 16px);
|
|
4365
|
+
line-height: 1;
|
|
4366
|
+
flex: 0 0 auto;
|
|
4367
|
+
}
|
|
4368
|
+
.twm-btn__label { line-height: 1; }
|
|
3575
4369
|
.twm-btn--primary {
|
|
3576
4370
|
background: var(--ea-accent);
|
|
3577
4371
|
border-color: var(--ea-accent);
|
|
@@ -4366,6 +5160,18 @@ button.twm-about-dialog__btn--close:focus {
|
|
|
4366
5160
|
position: relative;
|
|
4367
5161
|
}
|
|
4368
5162
|
.twm-root {
|
|
5163
|
+
/* R1. THE CONTAINING BLOCK A DRAGGED WINDOW ESCAPES INTO. A window promoted
|
|
5164
|
+
* in place is contained to its leaf wrap (C21), which clips it; for the
|
|
5165
|
+
* length of a drag the WM re-parents it here instead, so it can be carried
|
|
5166
|
+
* across tile boundaries. Its `left`/`top` then resolve against this box,
|
|
5167
|
+
* and without a `position` they would resolve against whatever positioned
|
|
5168
|
+
* ancestor the embedder happens to have — usually `.twm-shell__main`, which
|
|
5169
|
+
* is the same rectangle, and occasionally the page, which is not.
|
|
5170
|
+
*
|
|
5171
|
+
* `relative` and nothing else, for the reason `.twm-leaf` gives at length:
|
|
5172
|
+
* `transform`, `filter` and `contain` would also capture the
|
|
5173
|
+
* `position: fixed` dropdowns that deliberately escape their tile. */
|
|
5174
|
+
position: relative;
|
|
4369
5175
|
display: flex;
|
|
4370
5176
|
height: 100%;
|
|
4371
5177
|
width: 100%;
|
|
@@ -4412,6 +5218,15 @@ body.twm-dragging .twm-splitter { background: var(--accent-bright); opacity: 0.5
|
|
|
4412
5218
|
body.twm-dragging * { user-select: none; }
|
|
4413
5219
|
/* Leaf — chrome + body. */
|
|
4414
5220
|
.twm-leaf {
|
|
5221
|
+
/* C21. THE CONTAINING BLOCK for a window promoted in place. `overflow:
|
|
5222
|
+
* hidden` below is what actually confines it; this is what makes its
|
|
5223
|
+
* coordinates mean the pane rather than the page.
|
|
5224
|
+
*
|
|
5225
|
+
* `position: relative` and NOTHING ELSE — `transform`, `filter`, `contain`
|
|
5226
|
+
* and `will-change` also create a containing block AND capture
|
|
5227
|
+
* `position: fixed` descendants, which would clip the dropdowns DataTable
|
|
5228
|
+
* opens on purpose. */
|
|
5229
|
+
position: relative;
|
|
4415
5230
|
display: flex;
|
|
4416
5231
|
flex-direction: column;
|
|
4417
5232
|
flex: 1 1 auto;
|
|
@@ -4453,15 +5268,67 @@ body.twm-dragging * { user-select: none; }
|
|
|
4453
5268
|
background: var(--surface-2);
|
|
4454
5269
|
color: var(--text-primary);
|
|
4455
5270
|
}
|
|
5271
|
+
/* NO `text-transform` HERE, and the absence is the decision. This element used
|
|
5272
|
+
* to be lower-cased, which reads as a house style until you notice whose text
|
|
5273
|
+
* it is: the embedder's. `tile_renderer` puts a content factory's `title` in
|
|
5274
|
+
* here, and in a data application that is a record's display name — typed by a
|
|
5275
|
+
* user who capitalised "Customer Orders" on purpose. A framework may style its
|
|
5276
|
+
* own chrome however it likes; it may not rewrite its embedder's data on the
|
|
5277
|
+
* way to the screen. `.twm-leaf__tab-label` shows the same strings and lost the
|
|
5278
|
+
* same rule for the same reason. */
|
|
4456
5279
|
.twm-leaf__title {
|
|
4457
5280
|
color: var(--text-secondary);
|
|
4458
5281
|
white-space: nowrap;
|
|
4459
5282
|
text-overflow: ellipsis;
|
|
4460
5283
|
overflow: hidden;
|
|
4461
|
-
text-transform: lowercase;
|
|
4462
5284
|
letter-spacing: 0.02em;
|
|
4463
5285
|
}
|
|
4464
|
-
.
|
|
5286
|
+
/* C19. The tile's own glyph, left of the title, so a pane and the floating
|
|
5287
|
+
* window it promotes into look like the same thing — `.twm-managed-window__icon`
|
|
5288
|
+
* has always drawn one. Sized to the BUTTONS at the other end of the strip
|
|
5289
|
+
* rather than to the title, because those are the other icons in this bar.
|
|
5290
|
+
*
|
|
5291
|
+
* AND THE GLYPH HAS TO BE TOLD, exactly as on `.twm-leaf__btn` below: Material
|
|
5292
|
+
* Symbols is a font and renders at `font-size`, which defaults to 24px. Leaving
|
|
5293
|
+
* this off does not give a slightly large icon — it gives one half again as
|
|
5294
|
+
* tall as the 22px bar it sits in, which pushes the chrome open. */
|
|
5295
|
+
/* THE COMPOUND SELECTOR IS THE POINT.
|
|
5296
|
+
*
|
|
5297
|
+
* The element is `<span class="twm-leaf__icon material-symbols-outlined">`, and
|
|
5298
|
+
* Google's own stylesheet — which every consumer loads, AFTER this one — sets
|
|
5299
|
+
* `.material-symbols-outlined { font-size: 24px }`. Two single-class selectors
|
|
5300
|
+
* tie on specificity, so the LAST one wins, and the last one is theirs. A rule
|
|
5301
|
+
* written `.twm-leaf__icon { font-size: 14px }` therefore exists, is valid,
|
|
5302
|
+
* looks correct in review, and does nothing.
|
|
5303
|
+
*
|
|
5304
|
+
* `.twm-leaf__icon.material-symbols-outlined` is two classes and wins outright.
|
|
5305
|
+
* The same tie is why `.twm-panel-toggle-btn .material-symbols-outlined` works
|
|
5306
|
+
* — a descendant selector is also two — and it is the fourth outing of the same
|
|
5307
|
+
* defect family in this codebase: a rule for a class the other side already
|
|
5308
|
+
* claims. */
|
|
5309
|
+
.twm-leaf__icon,
|
|
5310
|
+
.twm-leaf__icon.material-symbols-outlined {
|
|
5311
|
+
flex: 0 0 auto;
|
|
5312
|
+
font-size: 14px;
|
|
5313
|
+
line-height: 1;
|
|
5314
|
+
color: var(--text-muted);
|
|
5315
|
+
font-variation-settings: 'opsz' 20, 'wght' 400;
|
|
5316
|
+
}
|
|
5317
|
+
/* Hidden, not empty, for a kind that has no glyph — the framework's own
|
|
5318
|
+
* `panel:*` tiles are in nobody's taxonomy. The chrome is a flex row with a
|
|
5319
|
+
* gap, and an empty span still takes its gap: the title would sit a glyph's
|
|
5320
|
+
* width off the left margin with nothing in front of it. */
|
|
5321
|
+
.twm-leaf__icon[hidden] { display: none; }
|
|
5322
|
+
.twm-leaf__actions { display: inline-flex; gap: 2px; align-items: center; }
|
|
5323
|
+
/* C18. The embedder's own buttons, to the LEFT of the structural ones and
|
|
5324
|
+
* separated from them — these act on the CONTENT, those act on the TILE. */
|
|
5325
|
+
.twm-leaf__actions--content {
|
|
5326
|
+
margin-left: auto;
|
|
5327
|
+
padding-right: var(--space-sm);
|
|
5328
|
+
margin-right: var(--space-sm);
|
|
5329
|
+
border-right: 1px solid var(--border-subtle);
|
|
5330
|
+
}
|
|
5331
|
+
.twm-leaf__actions--content[hidden] { display: none; }
|
|
4465
5332
|
.twm-leaf__btn {
|
|
4466
5333
|
display: inline-flex;
|
|
4467
5334
|
align-items: center;
|
|
@@ -4475,7 +5342,27 @@ body.twm-dragging * { user-select: none; }
|
|
|
4475
5342
|
cursor: pointer;
|
|
4476
5343
|
border-radius: 0;
|
|
4477
5344
|
}
|
|
4478
|
-
.
|
|
5345
|
+
/* THE GLYPH HAS TO BE TOLD. Material Symbols ships at `font-size: 24px`, and
|
|
5346
|
+
* the button is 18px square — so every icon overflowed its own button, the
|
|
5347
|
+
* strip read as oversized next to a 22px chrome bar, and the buttons visually
|
|
5348
|
+
* collided. `font-size` IS the glyph size for an icon font; `width`/`height` on
|
|
5349
|
+
* the button does not constrain it. */
|
|
5350
|
+
.twm-leaf__btn .material-symbols-outlined {
|
|
5351
|
+
font-size: 14px;
|
|
5352
|
+
line-height: 1;
|
|
5353
|
+
/* The variable-font axes the family exposes. At 14px the default optical
|
|
5354
|
+
* size (24) draws strokes too heavy to read as a small control. */
|
|
5355
|
+
font-variation-settings: 'opsz' 20, 'wght' 400;
|
|
5356
|
+
}
|
|
5357
|
+
.twm-leaf__btn:hover:not(:disabled) { background: var(--state-hover); color: var(--text-primary); }
|
|
5358
|
+
/* C20. A verb that applies to this kind of pane but not right now — greyed and
|
|
5359
|
+
* explained by its tooltip, rather than removed. `:disabled` alone would still
|
|
5360
|
+
* take the hover highlight above, which reads as clickable. */
|
|
5361
|
+
.twm-leaf__btn--disabled,
|
|
5362
|
+
.twm-leaf__btn:disabled {
|
|
5363
|
+
opacity: 0.35;
|
|
5364
|
+
cursor: default;
|
|
5365
|
+
}
|
|
4479
5366
|
.twm-leaf__body {
|
|
4480
5367
|
flex: 1 1 auto;
|
|
4481
5368
|
overflow: auto;
|
|
@@ -4489,7 +5376,15 @@ body.twm-dragging * { user-select: none; }
|
|
|
4489
5376
|
*
|
|
4490
5377
|
* The hamburger button on the left opens the per-tile "browse this
|
|
4491
5378
|
* page's content" menu (`wm.ctx.onTileTabMenu`). */
|
|
4492
|
-
.
|
|
5379
|
+
/* R8. THE SAME STRIP, IN A WINDOW. A pane floated into a managed window takes
|
|
5380
|
+
* its tabs with it — *"to window includes the tab-strip"* — and the strip in
|
|
5381
|
+
* the window is the strip from the pane: same component, same vocabulary, same
|
|
5382
|
+
* height. So it is the same rule, with a second selector, rather than a second
|
|
5383
|
+
* block that would drift from this one the first time either was touched.
|
|
5384
|
+
* `.twm-window-tabbar` is always the `--top` variant (a window's chrome is its
|
|
5385
|
+
* title bar, and the tabs go under it), so it appears in that block too. */
|
|
5386
|
+
.twm-leaf__tabbar,
|
|
5387
|
+
.twm-window-tabbar {
|
|
4493
5388
|
flex: 0 0 auto;
|
|
4494
5389
|
display: flex;
|
|
4495
5390
|
align-items: stretch;
|
|
@@ -4501,7 +5396,51 @@ body.twm-dragging * { user-select: none; }
|
|
|
4501
5396
|
user-select: none;
|
|
4502
5397
|
font-size: var(--font-size-xs);
|
|
4503
5398
|
}
|
|
4504
|
-
.twm-leaf__tabbar--hidden
|
|
5399
|
+
.twm-leaf__tabbar--hidden,
|
|
5400
|
+
.twm-window-tabbar--hidden { display: none; }
|
|
5401
|
+
|
|
5402
|
+
/* ══════════════════════════════════════════════════════════════════════
|
|
5403
|
+
* C33. A TAB DRAGGED INTO A TILE
|
|
5404
|
+
*
|
|
5405
|
+
* Mirrored by hand from `css/overrides.css`, where the source lives with the
|
|
5406
|
+
* rest of the `.twm-leaf*` rules. This sheet is the only one `build.mjs`
|
|
5407
|
+
* copies into `dist/`, and its generator (`scripts/css_extract.py`) is not in
|
|
5408
|
+
* this repository — so a rule written into overrides.css and not repeated
|
|
5409
|
+
* here does not exist for a consumer.
|
|
5410
|
+
*
|
|
5411
|
+
* THE ZONE RECTANGLE IS NOT STYLED HERE. It reuses `.twm-snap-preview
|
|
5412
|
+
* .twm-snap-preview--viewport` above, which is the window drop's own preview
|
|
5413
|
+
* element, so a tab drop and a window drop cannot come to disagree about what
|
|
5414
|
+
* a drop looks like.
|
|
5415
|
+
*
|
|
5416
|
+
* AND THERE IS NO `cursor` RULE, which is not an omission. For the length of a
|
|
5417
|
+
* native drag the browser draws the pointer from `dataTransfer.dropEffect`; a
|
|
5418
|
+
* `cursor` on the drop target is ignored throughout.
|
|
5419
|
+
* ══════════════════════════════════════════════════════════════════════ */
|
|
5420
|
+
|
|
5421
|
+
/* THE PANE UNDER THE POINTER. The preview rectangle says what a release would
|
|
5422
|
+
* PRODUCE; this says which pane is being asked. An inset OUTLINE rather than a
|
|
5423
|
+
* border, because a border changes the box and every tile in the row would
|
|
5424
|
+
* reflow by a pixel as the pointer crossed between them. */
|
|
5425
|
+
.twm-leaf--drop-target {
|
|
5426
|
+
outline: 2px solid var(--accent-bright, #1683c4);
|
|
5427
|
+
outline-offset: -2px;
|
|
5428
|
+
}
|
|
5429
|
+
|
|
5430
|
+
/* A STRIP ARMED TO RECEIVE — the drop that adds the tab at the end of this
|
|
5431
|
+
* tile's list rather than into a zone of its body. */
|
|
5432
|
+
.twm-leaf__tabbar--drop-target {
|
|
5433
|
+
background: color-mix(in srgb, var(--accent-bright, #1683c4) 18%, transparent);
|
|
5434
|
+
box-shadow: inset 0 0 0 1px var(--accent-bright, #1683c4);
|
|
5435
|
+
}
|
|
5436
|
+
|
|
5437
|
+
/* THE TAB BEING CARRIED. `DragReorder` has put `.dragging` on the TOP strip's
|
|
5438
|
+
* tab since it was written and `.notebook-tabs > .tab.dragging` below dims it;
|
|
5439
|
+
* the bottom strip's hand-rolled reorder set no class at all, so a drag there
|
|
5440
|
+
* looked exactly like a click that did nothing. One declaration for both
|
|
5441
|
+
* strips. */
|
|
5442
|
+
.twm-leaf__tab.dragging { opacity: 0.4; }
|
|
5443
|
+
|
|
4505
5444
|
.twm-leaf__tab-hamburger {
|
|
4506
5445
|
flex: 0 0 auto;
|
|
4507
5446
|
display: inline-flex;
|
|
@@ -4576,11 +5515,12 @@ body.twm-dragging * { user-select: none; }
|
|
|
4576
5515
|
border-top: 1px solid var(--accent-bright, #1683c4);
|
|
4577
5516
|
clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
|
|
4578
5517
|
}
|
|
5518
|
+
/* Lower-casing removed here too — a tab label is the same embedder-supplied
|
|
5519
|
+
* title as `.twm-leaf__title`, just in the strip at the bottom of the leaf. */
|
|
4579
5520
|
.twm-leaf__tab-label {
|
|
4580
5521
|
overflow: hidden;
|
|
4581
5522
|
text-overflow: ellipsis;
|
|
4582
5523
|
max-width: 160px;
|
|
4583
|
-
text-transform: lowercase;
|
|
4584
5524
|
letter-spacing: 0.02em;
|
|
4585
5525
|
}
|
|
4586
5526
|
.twm-leaf__tab-close {
|
|
@@ -4602,6 +5542,293 @@ body.twm-dragging * { user-select: none; }
|
|
|
4602
5542
|
opacity: 1;
|
|
4603
5543
|
color: var(--text-bright);
|
|
4604
5544
|
}
|
|
5545
|
+
|
|
5546
|
+
/* ══ C22. THE TOP TAB LAYOUT ═════════════════════════════════════════
|
|
5547
|
+
*
|
|
5548
|
+
* `tabLayout: 'top'` puts the strip BETWEEN the chrome and the body and draws
|
|
5549
|
+
* it with `NotebookTabBar` — the editor's tab bar — instead of the spreadsheet
|
|
5550
|
+
* strip above. The bar element is the same `.twm-leaf__tabbar`, so everything
|
|
5551
|
+
* it already carries (the fixed height, `--hidden`, `flex: 0 0 auto`) still
|
|
5552
|
+
* applies; this modifier only turns the strip the right way up.
|
|
5553
|
+
*
|
|
5554
|
+
* The border moves with it. A strip at the bottom of a tile is separated from
|
|
5555
|
+
* the body above it by a top border; a strip at the top is separated from the
|
|
5556
|
+
* body BELOW it by a bottom one, and keeping the top border as well would draw
|
|
5557
|
+
* a second line a pixel under the chrome's own. */
|
|
5558
|
+
.twm-leaf__tabbar--top,
|
|
5559
|
+
.twm-window-tabbar {
|
|
5560
|
+
border-top: none;
|
|
5561
|
+
border-bottom: 1px solid var(--border-default);
|
|
5562
|
+
/* The editor tabs are a touch taller than a 22px spreadsheet tab: they
|
|
5563
|
+
* carry a glyph, a label and a close button on one line, and 22px leaves
|
|
5564
|
+
* the close button touching the label's descenders. */
|
|
5565
|
+
height: 26px;
|
|
5566
|
+
padding: 0;
|
|
5567
|
+
/* `overflow: hidden` on the bottom strip clips the horizontal scroll the
|
|
5568
|
+
* tab list wants. The list scrolls itself (below); the BAR must not. */
|
|
5569
|
+
overflow: visible;
|
|
5570
|
+
}
|
|
5571
|
+
/* `NotebookTabBar.mount()` assigns `className = 'tabs notebook-tabs'` to
|
|
5572
|
+
* whatever container it is handed (`notebook_tab_bar.js:61`) — it does not add
|
|
5573
|
+
* to it — which is why the renderer mounts it into a child of the bar rather
|
|
5574
|
+
* than into the bar. The child is that container, and it fills the bar. */
|
|
5575
|
+
.twm-leaf__tabbar--top > .notebook-tabs,
|
|
5576
|
+
.twm-window-tabbar > .notebook-tabs { flex: 1 1 auto; min-width: 0; }
|
|
5577
|
+
/* A window's body is what the content mounts into, below the strip. It is
|
|
5578
|
+
* the box `.twm-managed-window__content` used to be for a window with no
|
|
5579
|
+
* tabs, so it carries the same three declarations — without them a grid
|
|
5580
|
+
* inside a floated pane sizes to its rows rather than to the window and the
|
|
5581
|
+
* strip is pushed off the bottom edge. */
|
|
5582
|
+
.twm-window-body { flex: 1 1 auto; min-height: 0; min-width: 0; }
|
|
5583
|
+
|
|
5584
|
+
/* ══ NotebookTabBar ══════════════════════════════════════════════════
|
|
5585
|
+
*
|
|
5586
|
+
* A CLASS ONE SIDE KNOWS AND THE OTHER HAD NEVER HEARD OF — the fifth in this
|
|
5587
|
+
* codebase and the first one found before it shipped. `NotebookTabBar` has
|
|
5588
|
+
* rendered `.tabs` / `.tab` / `.tab-icon` / `.twm-tab-title` / `.tab-dirty` /
|
|
5589
|
+
* `.twm-tab-close` / `.nb-context-menu` since it was extracted, and NOT ONE of
|
|
5590
|
+
* those selectors existed in any FlexDesk stylesheet: the component imports
|
|
5591
|
+
* cleanly, mounts without error, is fully interactive, and draws as a row of
|
|
5592
|
+
* unstyled inline text with a `×` after each label. Nothing throws and no test
|
|
5593
|
+
* can see it, which is the signature of the whole family.
|
|
5594
|
+
*
|
|
5595
|
+
* The visual language is the tile chrome's, not an editor's: `--surface-1` bar,
|
|
5596
|
+
* `--surface-0` active tab so it reads as continuous with the body under it,
|
|
5597
|
+
* square corners, `--font-size-xs`. A tab bar drawn in one house style with the
|
|
5598
|
+
* chrome an inch above it in another is how a pane stops looking like one
|
|
5599
|
+
* object. */
|
|
5600
|
+
.tabs.notebook-tabs {
|
|
5601
|
+
display: flex;
|
|
5602
|
+
align-items: stretch;
|
|
5603
|
+
height: 100%;
|
|
5604
|
+
min-width: 0;
|
|
5605
|
+
background: var(--surface-1);
|
|
5606
|
+
user-select: none;
|
|
5607
|
+
font-size: var(--font-size-xs);
|
|
5608
|
+
/* SCROLL, DO NOT WRAP OR SHRINK. The tabs keep their width and the strip
|
|
5609
|
+
* scrolls under them — a tab that shrinks to nothing as you open the tenth
|
|
5610
|
+
* one is a tab you cannot read or aim at. The scrollbar itself is hidden:
|
|
5611
|
+
* it is a 26px strip, and a horizontal bar in it would eat a third of the
|
|
5612
|
+
* label. Wheel and drag still scroll it, and DragReorder auto-scrolls the
|
|
5613
|
+
* edges while a tab is being dragged (`drag_reorder.js:_autoScrollEdge`). */
|
|
5614
|
+
overflow-x: auto;
|
|
5615
|
+
overflow-y: hidden;
|
|
5616
|
+
scrollbar-width: none;
|
|
5617
|
+
}
|
|
5618
|
+
.tabs.notebook-tabs::-webkit-scrollbar { display: none; }
|
|
5619
|
+
.notebook-tabs > .tab {
|
|
5620
|
+
position: relative;
|
|
5621
|
+
flex: 0 0 auto;
|
|
5622
|
+
display: inline-flex;
|
|
5623
|
+
align-items: center;
|
|
5624
|
+
gap: var(--space-xs);
|
|
5625
|
+
max-width: 200px;
|
|
5626
|
+
padding: 0 var(--space-sm);
|
|
5627
|
+
background: var(--surface-1);
|
|
5628
|
+
color: var(--text-muted);
|
|
5629
|
+
border: none;
|
|
5630
|
+
border-right: 1px solid var(--border-subtle);
|
|
5631
|
+
cursor: pointer;
|
|
5632
|
+
line-height: 1;
|
|
5633
|
+
white-space: nowrap;
|
|
5634
|
+
}
|
|
5635
|
+
.notebook-tabs > .tab:hover { background: var(--surface-2); color: var(--text-primary); }
|
|
5636
|
+
/* The active tab is the body's colour and loses the line under it, so the tab
|
|
5637
|
+
* and the pane it shows read as one surface. The accent sits on TOP — the edge
|
|
5638
|
+
* furthest from the body — for the same reason a browser puts it there: it
|
|
5639
|
+
* marks the tab, not the boundary. */
|
|
5640
|
+
.notebook-tabs > .tab.active {
|
|
5641
|
+
background: var(--surface-0);
|
|
5642
|
+
color: var(--text-bright);
|
|
5643
|
+
}
|
|
5644
|
+
.notebook-tabs > .tab.active::before {
|
|
5645
|
+
content: '';
|
|
5646
|
+
position: absolute;
|
|
5647
|
+
inset: 0 0 auto 0;
|
|
5648
|
+
height: 2px;
|
|
5649
|
+
pointer-events: none;
|
|
5650
|
+
background: var(--accent-bright, #1683c4);
|
|
5651
|
+
}
|
|
5652
|
+
/* A PREVIEW tab — opened by a single click and replaced by the next one.
|
|
5653
|
+
* Italic is the convention every editor uses for "this one is not staying". */
|
|
5654
|
+
.notebook-tabs > .tab.tab--preview { font-style: italic; }
|
|
5655
|
+
/* THE GLYPH HAS TO BE TOLD, and it has to be told in TWO selectors.
|
|
5656
|
+
*
|
|
5657
|
+
* `<span class="material-symbols-outlined tab-icon">` carries two classes, and
|
|
5658
|
+
* Google's stylesheet — loaded by every consumer, AFTER this one — sets
|
|
5659
|
+
* `.material-symbols-outlined { font-size: 24px }`. A single-class rule here
|
|
5660
|
+
* ties on specificity and loses to whichever came last, which is theirs. The
|
|
5661
|
+
* compound selector is two classes and wins outright. Fourth outing of this
|
|
5662
|
+
* exact defect; it is why `.twm-leaf__icon` is written the same way. */
|
|
5663
|
+
.notebook-tabs .tab-icon,
|
|
5664
|
+
.notebook-tabs .tab-icon.material-symbols-outlined {
|
|
5665
|
+
flex: 0 0 auto;
|
|
5666
|
+
font-size: 14px;
|
|
5667
|
+
line-height: 1;
|
|
5668
|
+
font-variation-settings: 'opsz' 20, 'wght' 400;
|
|
5669
|
+
}
|
|
5670
|
+
.notebook-tabs .tab-icon[hidden] { display: none; }
|
|
5671
|
+
/* The label. NOT lower-cased and not capitalised: it is the embedder's own
|
|
5672
|
+
* string — a record's display name, typed by a user who capitalised it on
|
|
5673
|
+
* purpose — for the same reason `.twm-leaf__title` lost that rule.
|
|
5674
|
+
*
|
|
5675
|
+
* AND IT NEEDS ITS OWN `line-height`, WHICH IS THE FIX FOR CLIPPED DESCENDERS.
|
|
5676
|
+
* `.tab` sets `line-height: 1` so the row's boxes stack predictably in a 26px
|
|
5677
|
+
* strip; this span sets `overflow: hidden` for the ellipsis, which makes it a
|
|
5678
|
+
* scroll container whose content box is exactly `1 × font-size` tall. Anything
|
|
5679
|
+
* drawn below the baseline — the tail of a `g`, a `y`, a `p` — falls outside
|
|
5680
|
+
* that box and is CLIPPED. It reads as a tab label with its bottom shaved off,
|
|
5681
|
+
* which is precisely how it was reported ("fix the cut-off text in the tabs -
|
|
5682
|
+
* cut off at bottom"), and it is invisible on any label that happens to have no
|
|
5683
|
+
* descender in it.
|
|
5684
|
+
*
|
|
5685
|
+
* 1.45 is the smallest value that clears the descender at every size this strip
|
|
5686
|
+
* is used at; the flex row still centres the span, so the tab's own height and
|
|
5687
|
+
* every other box in it are unchanged. */
|
|
5688
|
+
.notebook-tabs .twm-tab-title {
|
|
5689
|
+
overflow: hidden;
|
|
5690
|
+
text-overflow: ellipsis;
|
|
5691
|
+
letter-spacing: 0.02em;
|
|
5692
|
+
line-height: 1.45;
|
|
5693
|
+
}
|
|
5694
|
+
/* Unsaved work. A dot rather than a `*`, and it takes the close button's slot
|
|
5695
|
+
* rather than adding to the row — in most editors the two swap on hover, but a
|
|
5696
|
+
* dot that MOVES the close button as it appears makes the button a target that
|
|
5697
|
+
* shifts under the pointer. */
|
|
5698
|
+
.notebook-tabs .tab-dirty {
|
|
5699
|
+
flex: 0 0 auto;
|
|
5700
|
+
font-size: 9px;
|
|
5701
|
+
line-height: 1;
|
|
5702
|
+
color: var(--accent-bright, #1683c4);
|
|
5703
|
+
}
|
|
5704
|
+
.notebook-tabs .twm-tab-close {
|
|
5705
|
+
flex: 0 0 auto;
|
|
5706
|
+
display: inline-flex;
|
|
5707
|
+
align-items: center;
|
|
5708
|
+
justify-content: center;
|
|
5709
|
+
width: 14px;
|
|
5710
|
+
height: 14px;
|
|
5711
|
+
padding: 0;
|
|
5712
|
+
background: transparent;
|
|
5713
|
+
border: none;
|
|
5714
|
+
border-radius: var(--radius-sm);
|
|
5715
|
+
color: inherit;
|
|
5716
|
+
/* A `×` CHARACTER, not a Material Symbol — the component writes the glyph
|
|
5717
|
+
* itself (`notebook_tab_bar.js:168`), so this one is sized by `font-size`
|
|
5718
|
+
* on the button and there is no icon font to out-specify. */
|
|
5719
|
+
font-size: 13px;
|
|
5720
|
+
line-height: 1;
|
|
5721
|
+
opacity: 0.55;
|
|
5722
|
+
cursor: pointer;
|
|
5723
|
+
}
|
|
5724
|
+
.notebook-tabs .twm-tab-close:hover {
|
|
5725
|
+
opacity: 1;
|
|
5726
|
+
background: var(--state-hover);
|
|
5727
|
+
color: var(--text-bright);
|
|
5728
|
+
}
|
|
5729
|
+
/* ── Reordering ──────────────────────────────────────────────────────
|
|
5730
|
+
* `DragReorder` puts `.dragging` on the tab under the pointer and inserts a
|
|
5731
|
+
* `.twm-tab-drop-indicator` element at the position it would land
|
|
5732
|
+
* (`drag_reorder.js:_start`, `_ensureIndicator`). Neither had a rule, so a
|
|
5733
|
+
* drag looked exactly like a click that did nothing until the tab arrived
|
|
5734
|
+
* somewhere else. */
|
|
5735
|
+
.notebook-tabs > .tab.dragging { opacity: 0.4; }
|
|
5736
|
+
.twm-tab-drop-indicator {
|
|
5737
|
+
flex: 0 0 auto;
|
|
5738
|
+
align-self: stretch;
|
|
5739
|
+
width: 2px;
|
|
5740
|
+
background: var(--accent-bright, #1683c4);
|
|
5741
|
+
pointer-events: none;
|
|
5742
|
+
}
|
|
5743
|
+
|
|
5744
|
+
/* ── C33. TWO MORE OF THE CLASS-ONE-SIDE-KNOWS FAMILY ────────────────
|
|
5745
|
+
*
|
|
5746
|
+
* Mirrored by hand from `css/base.css`. Both have been set by JavaScript
|
|
5747
|
+
* since the day they were written and defined by NO stylesheet in this
|
|
5748
|
+
* repository, so a cross-pane drop has never drawn anything anywhere:
|
|
5749
|
+
*
|
|
5750
|
+
* `.notebook-tabs--drop-target` `notebook_tab_bar.js`,
|
|
5751
|
+
* `#onStripDragOver`
|
|
5752
|
+
* `.notebook-editor-container--drop-target` `editor/editor_pane.js`,
|
|
5753
|
+
* its own `dragover`
|
|
5754
|
+
*
|
|
5755
|
+
* Fixed alongside C33 rather than left alone, because C33 adds a THIRD
|
|
5756
|
+
* `--drop-target` affordance beside them and shipping a live one next to two
|
|
5757
|
+
* dead ones is how the family keeps growing. Same declarations as
|
|
5758
|
+
* `.twm-leaf__tabbar--drop-target` above, deliberately: three surfaces that
|
|
5759
|
+
* mean "let go here and this list takes it" should look like one thing. */
|
|
5760
|
+
.notebook-tabs--drop-target,
|
|
5761
|
+
.notebook-editor-container--drop-target {
|
|
5762
|
+
background: color-mix(in srgb, var(--accent-bright, #1683c4) 18%, transparent);
|
|
5763
|
+
box-shadow: inset 0 0 0 1px var(--accent-bright, #1683c4);
|
|
5764
|
+
}
|
|
5765
|
+
/* ── The tab bar's own context menu ──────────────────────────────────
|
|
5766
|
+
* Reached only by a consumer that lets the component open it — the tile
|
|
5767
|
+
* renderer suppresses it in favour of the WM's single tab menu, so these rules
|
|
5768
|
+
* are for `EditorPane` and anyone else mounting a `NotebookTabBar` directly.
|
|
5769
|
+
* Same geometry and the same tokens as `.twm-context-menu`, because two
|
|
5770
|
+
* context menus in one application that disagree about their own padding is
|
|
5771
|
+
* how a UI stops feeling assembled. */
|
|
5772
|
+
.nb-context-menu {
|
|
5773
|
+
position: fixed;
|
|
5774
|
+
z-index: 10050;
|
|
5775
|
+
min-width: 180px;
|
|
5776
|
+
padding: var(--space-xxs) 0;
|
|
5777
|
+
background: var(--surface-2);
|
|
5778
|
+
border: 1px solid var(--border-strong);
|
|
5779
|
+
border-radius: var(--radius-sm);
|
|
5780
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
|
|
5781
|
+
font-size: var(--font-size-xs);
|
|
5782
|
+
/* Opened hidden and revealed on the next frame (`#showContextMenu` adds
|
|
5783
|
+
* `.visible` in a rAF) so it can fade rather than snap — the same
|
|
5784
|
+
* open-then-reveal the collapsible primitive uses, and the same reason its
|
|
5785
|
+
* `.visible` rule had to exist before anything worked. */
|
|
5786
|
+
opacity: 0;
|
|
5787
|
+
transition: opacity 90ms ease-out;
|
|
5788
|
+
}
|
|
5789
|
+
.nb-context-menu.visible { opacity: 1; }
|
|
5790
|
+
.nb-context-menu__item {
|
|
5791
|
+
display: flex;
|
|
5792
|
+
align-items: center;
|
|
5793
|
+
gap: var(--space-sm);
|
|
5794
|
+
width: 100%;
|
|
5795
|
+
padding: var(--space-xs) var(--space-md);
|
|
5796
|
+
background: transparent;
|
|
5797
|
+
border: none;
|
|
5798
|
+
color: var(--text-primary);
|
|
5799
|
+
text-align: left;
|
|
5800
|
+
cursor: pointer;
|
|
5801
|
+
white-space: nowrap;
|
|
5802
|
+
}
|
|
5803
|
+
.nb-context-menu__item:hover { background: var(--state-hover); }
|
|
5804
|
+
.nb-context-menu__item .material-symbols-outlined {
|
|
5805
|
+
font-size: 14px;
|
|
5806
|
+
line-height: 1;
|
|
5807
|
+
color: var(--text-muted);
|
|
5808
|
+
font-variation-settings: 'opsz' 20, 'wght' 400;
|
|
5809
|
+
}
|
|
5810
|
+
.nb-context-menu__item--danger { color: var(--color-danger, #c74e4e); }
|
|
5811
|
+
.nb-context-menu__item--danger .material-symbols-outlined { color: inherit; }
|
|
5812
|
+
/* DISABLED MEANS DISABLED. `.twm-context-menu-item.disabled` was the third
|
|
5813
|
+
* instance of this family — the JS had always set the class, no rule existed,
|
|
5814
|
+
* and a disabled item rendered at full opacity with a pointer cursor and did
|
|
5815
|
+
* nothing when clicked. `#showContextMenu` binds no listener on a disabled
|
|
5816
|
+
* item, so `pointer-events: none` is belt to that braces. */
|
|
5817
|
+
.nb-context-menu__item--disabled {
|
|
5818
|
+
opacity: 0.4;
|
|
5819
|
+
cursor: default;
|
|
5820
|
+
pointer-events: none;
|
|
5821
|
+
}
|
|
5822
|
+
.nb-context-menu__shortcut {
|
|
5823
|
+
margin-left: auto;
|
|
5824
|
+
padding-left: var(--space-lg);
|
|
5825
|
+
color: var(--text-dim);
|
|
5826
|
+
}
|
|
5827
|
+
.nb-context-menu__separator {
|
|
5828
|
+
height: 1px;
|
|
5829
|
+
margin: var(--space-xxs) 0;
|
|
5830
|
+
background: var(--border-subtle);
|
|
5831
|
+
}
|
|
4605
5832
|
/* ── Tile tab menu (hamburger pop-out) ────────────────────────────
|
|
4606
5833
|
* Anchored above the hamburger button. Lists every entity that the
|
|
4607
5834
|
* tile's home page would show, grouped by source, with search +
|
|
@@ -4752,6 +5979,72 @@ body.twm-dragging * { user-select: none; }
|
|
|
4752
5979
|
cursor: not-allowed;
|
|
4753
5980
|
}
|
|
4754
5981
|
|
|
5982
|
+
/* Tab switcher — the leaf hamburger's open-tabs list. A compact menu
|
|
5983
|
+
* anchored above the hamburger for quick switching between the tile's
|
|
5984
|
+
* open tabs (distinct from the content browser above). */
|
|
5985
|
+
.twm-tile-tabswitch-overlay {
|
|
5986
|
+
position: fixed;
|
|
5987
|
+
inset: 0;
|
|
5988
|
+
z-index: var(--z-modal);
|
|
5989
|
+
pointer-events: none;
|
|
5990
|
+
}
|
|
5991
|
+
.twm-tile-tabswitch {
|
|
5992
|
+
pointer-events: auto;
|
|
5993
|
+
width: 260px;
|
|
5994
|
+
display: flex;
|
|
5995
|
+
flex-direction: column;
|
|
5996
|
+
background: var(--surface-1);
|
|
5997
|
+
border: 1px solid var(--border-default);
|
|
5998
|
+
box-shadow: var(--shadow-lg);
|
|
5999
|
+
overflow: hidden;
|
|
6000
|
+
}
|
|
6001
|
+
.twm-tile-tabswitch__head {
|
|
6002
|
+
display: flex;
|
|
6003
|
+
align-items: center;
|
|
6004
|
+
gap: var(--space-sm);
|
|
6005
|
+
padding: var(--space-sm) var(--space-md);
|
|
6006
|
+
background: var(--surface-2);
|
|
6007
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
6008
|
+
font-size: var(--font-size-xs);
|
|
6009
|
+
text-transform: uppercase;
|
|
6010
|
+
letter-spacing: 0.04em;
|
|
6011
|
+
color: var(--text-secondary);
|
|
6012
|
+
}
|
|
6013
|
+
.twm-tile-tabswitch__head .material-symbols-outlined { font-size: 14px; }
|
|
6014
|
+
.twm-tile-tabswitch__head-label { flex: 1 1 auto; }
|
|
6015
|
+
.twm-tile-tabswitch__list {
|
|
6016
|
+
list-style: none;
|
|
6017
|
+
margin: 0;
|
|
6018
|
+
padding: 4px 0;
|
|
6019
|
+
overflow-y: auto;
|
|
6020
|
+
}
|
|
6021
|
+
.twm-tile-tabswitch__item {
|
|
6022
|
+
display: grid;
|
|
6023
|
+
grid-template-columns: 18px 1fr;
|
|
6024
|
+
align-items: center;
|
|
6025
|
+
gap: var(--space-xs);
|
|
6026
|
+
padding: 4px 12px;
|
|
6027
|
+
cursor: pointer;
|
|
6028
|
+
color: var(--text-secondary);
|
|
6029
|
+
font-size: var(--font-size-sm);
|
|
6030
|
+
}
|
|
6031
|
+
.twm-tile-tabswitch__item:hover,
|
|
6032
|
+
.twm-tile-tabswitch__item--cursor {
|
|
6033
|
+
background: var(--state-selected);
|
|
6034
|
+
color: var(--text-bright);
|
|
6035
|
+
}
|
|
6036
|
+
.twm-tile-tabswitch__item--cursor {
|
|
6037
|
+
outline: 1px solid var(--accent-bright, #1683c4);
|
|
6038
|
+
outline-offset: -1px;
|
|
6039
|
+
}
|
|
6040
|
+
.twm-tile-tabswitch__item--on { color: var(--text-bright); }
|
|
6041
|
+
.twm-tile-tabswitch__check { font-size: 14px; color: var(--accent-bright, #1683c4); }
|
|
6042
|
+
.twm-tile-tabswitch__label {
|
|
6043
|
+
overflow: hidden;
|
|
6044
|
+
text-overflow: ellipsis;
|
|
6045
|
+
white-space: nowrap;
|
|
6046
|
+
}
|
|
6047
|
+
|
|
4755
6048
|
/* ── from tiling/command_palette.css ───────────────────────── */
|
|
4756
6049
|
/* command_palette.css — Ctrl+K popup. */
|
|
4757
6050
|
|