flexdesk 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/css/base.css +2243 -872
- package/css/flexdesk.css +1375 -14
- 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 +1375 -14
- 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 +3140 -157
- 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 +156 -25
- 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 +115 -11
- package/src/tiling/wm.js +2375 -84
- package/src/tiling/zoom.js +248 -0
- 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/base.css
CHANGED
|
@@ -1131,6 +1131,13 @@ body.twm-dt-col-resizing {
|
|
|
1131
1131
|
.twm-collapsible-content {
|
|
1132
1132
|
display: none;
|
|
1133
1133
|
}
|
|
1134
|
+
/* C8. The rule `toggleCategory()` has always assumed and no stylesheet has ever
|
|
1135
|
+
* provided. Without it the collapsible primitive is non-functional as shipped:
|
|
1136
|
+
* the toggle adds `.visible` and nothing acts on the class, so a category that
|
|
1137
|
+
* "opens" stays `display: none`. One rule; it was simply missing. */
|
|
1138
|
+
.twm-collapsible-content.visible {
|
|
1139
|
+
display: block;
|
|
1140
|
+
}
|
|
1134
1141
|
/* ============================================
|
|
1135
1142
|
Autocomplete Field Component (legacy-variable style)
|
|
1136
1143
|
============================================ */
|
|
@@ -2342,1098 +2349,2462 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
2342
2349
|
cursor: default;
|
|
2343
2350
|
}
|
|
2344
2351
|
|
|
2345
|
-
/*
|
|
2346
|
-
|
|
2347
|
-
|
|
2352
|
+
/* ═══════════════════════════════════════════════════════════════════════
|
|
2353
|
+
@flexdesk/tiles — THE TILE GRID'S CHROME
|
|
2354
|
+
|
|
2355
|
+
THIS STYLESHEET HAD NEVER EXISTED. `src/tiles/` was extracted from
|
|
2356
|
+
EcoAgent's `ui/js/results/` with its JavaScript and without its CSS, so
|
|
2357
|
+
every consumer of `@flexdesk/tiles` got a `TileGrid` that ran, dragged,
|
|
2358
|
+
resized, saved layouts — and drew a column of unstyled `<div>`s.
|
|
2359
|
+
`TileGrid._init()` sets `--grid-columns`, `--grid-row-height` and
|
|
2360
|
+
`--grid-gap` on the grid element (`src/tiles/tile_grid.js`), and until
|
|
2361
|
+
these rules landed NOTHING IN ANY STYLESHEET READ THEM: no
|
|
2362
|
+
`display: grid`, no `grid-template-columns`, no `grid-auto-rows`. That is
|
|
2363
|
+
the CSS-side half of this repository's signature bug — not a class the JS
|
|
2364
|
+
sets and no rule defines, but the reverse, three custom properties written
|
|
2365
|
+
by the JS into a void.
|
|
2366
|
+
|
|
2367
|
+
THE CLASS NAMES ARE UNPREFIXED, deliberately and unusually. Everything
|
|
2368
|
+
else in this file is `twm-`; these are not, because the names are compiled
|
|
2369
|
+
into the JavaScript that was extracted (`className = 'tile-grid'`,
|
|
2370
|
+
`'tile-header'`, `classList.add('dragging')` …) and renaming them upstream
|
|
2371
|
+
would be a breaking change to every consumer for a cosmetic gain. The one
|
|
2372
|
+
place the prefix DOES appear — `.twm-tile-error`, below — is the exception
|
|
2373
|
+
that proves it: `showError()` emits the prefixed spelling, so the donor's
|
|
2374
|
+
`.tile-error` rule is NOT ported here. Porting it would have written a
|
|
2375
|
+
dead rule directly beside a live class, which is the mistake this comment
|
|
2376
|
+
exists to stop the next reader from repeating.
|
|
2377
|
+
|
|
2378
|
+
Values come from `tokens.css`, so swapping the tokens restyles the tiles
|
|
2379
|
+
with everything else. Two colour literals survive the port and each says at
|
|
2380
|
+
its own rule why: a light accent, for which there is no token (the nearest,
|
|
2381
|
+
`--accent-bright`, is a border colour on this background rather than a text
|
|
2382
|
+
one), and the error red already shipped in `.twm-tile-error`.
|
|
2383
|
+
Ported from EcoAgent `ui/css/results_widgets.css` (the donor still owns its
|
|
2384
|
+
own copy — it does not load this file).
|
|
2385
|
+
═══════════════════════════════════════════════════════════════════════ */
|
|
2386
|
+
|
|
2387
|
+
/* ── the grid container ──────────────────────────────────────────────── */
|
|
2388
|
+
/* The three custom properties are set by `TileGrid._init()` as inline style
|
|
2389
|
+
on this element; the fallbacks are the constructor's defaults, so a grid
|
|
2390
|
+
element built by hand still lays out. */
|
|
2391
|
+
.tile-grid {
|
|
2392
|
+
display: grid;
|
|
2393
|
+
grid-template-columns: repeat(var(--grid-columns, 12), 1fr);
|
|
2394
|
+
grid-auto-rows: var(--grid-row-height, 80px);
|
|
2395
|
+
gap: var(--grid-gap, 16px);
|
|
2396
|
+
padding: var(--grid-gap, 16px) 0 0 0;
|
|
2397
|
+
/* A floor rather than a height: the rows are what size the grid. It
|
|
2398
|
+
matters only when the board is empty, and an empty board that is zero
|
|
2399
|
+
pixels tall is a board you cannot drop the first tile onto. */
|
|
2400
|
+
min-height: 400px;
|
|
2401
|
+
position: relative;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
/* ── the built-in toolbar ────────────────────────────────────────────── */
|
|
2405
|
+
/* `_buildToolbar()` emits `tile-grid-toolbar tile-grid-toolbar--bottom` and no
|
|
2406
|
+
other variant, so the divider lives on the modifier: a rule that put a
|
|
2407
|
+
`border-bottom` on the base class — as the donor's did, when the toolbar sat
|
|
2408
|
+
above the grid — draws a line along the wrong edge here. A consumer that
|
|
2409
|
+
passes `showToolbar: false` and builds its own toolbar gets none of this. */
|
|
2410
|
+
.tile-grid-toolbar {
|
|
2348
2411
|
display: flex;
|
|
2349
|
-
|
|
2412
|
+
justify-content: space-between;
|
|
2350
2413
|
align-items: center;
|
|
2351
|
-
justify-content: center;
|
|
2352
|
-
height: 100%;
|
|
2353
2414
|
gap: var(--space-md);
|
|
2354
|
-
|
|
2355
|
-
|
|
2415
|
+
padding: var(--space-lg) var(--space-xl);
|
|
2416
|
+
background: rgba(255, 255, 255, 0.02);
|
|
2356
2417
|
}
|
|
2357
|
-
/* ===================================================================
|
|
2358
|
-
REUSABLE ACTION DROPDOWN COMPONENT
|
|
2359
|
-
Used by Add Scenario, Add Widget, and other action dropdowns
|
|
2360
|
-
=================================================================== */
|
|
2361
2418
|
|
|
2362
|
-
.
|
|
2363
|
-
|
|
2364
|
-
position: fixed;
|
|
2365
|
-
min-width: 220px;
|
|
2366
|
-
padding: var(--space-md);
|
|
2367
|
-
background: linear-gradient(145deg, rgba(30, 32, 38, 0.98), rgba(24, 26, 32, 0.96));
|
|
2368
|
-
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
2369
|
-
border-radius: 10px;
|
|
2370
|
-
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);
|
|
2371
|
-
z-index: var(--z-toast);
|
|
2372
|
-
opacity: 0;
|
|
2373
|
-
pointer-events: none;
|
|
2374
|
-
transition: opacity 0.16s ease;
|
|
2375
|
-
}
|
|
2376
|
-
.twm-action-dropdown-menu[hidden] {
|
|
2377
|
-
display: none !important;
|
|
2419
|
+
.tile-grid-toolbar--bottom {
|
|
2420
|
+
border-top: 1px solid var(--border-subtle);
|
|
2378
2421
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2422
|
+
|
|
2423
|
+
/* `toolbar-group`, `toolbar-btn`, `btn-text` and `dropdown-arrow` are names
|
|
2424
|
+
generic enough that a consumer almost certainly has its own. They are
|
|
2425
|
+
scoped under the toolbar rather than claimed globally — a library that
|
|
2426
|
+
defines a bare `.toolbar-btn` restyles buttons it has never met. */
|
|
2427
|
+
.tile-grid-toolbar .toolbar-group {
|
|
2381
2428
|
display: flex;
|
|
2382
2429
|
align-items: center;
|
|
2383
|
-
gap:
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2430
|
+
gap: var(--space-md);
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
.tile-grid-toolbar .add-widget-container {
|
|
2434
|
+
position: relative;
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
.tile-grid-toolbar .toolbar-btn {
|
|
2438
|
+
display: inline-flex;
|
|
2439
|
+
align-items: center;
|
|
2440
|
+
gap: var(--space-xs);
|
|
2441
|
+
padding: var(--space-xs) var(--space-lg);
|
|
2442
|
+
background: rgba(255, 255, 255, 0.04);
|
|
2443
|
+
border: 1px solid var(--border-default);
|
|
2444
|
+
border-radius: var(--radius-sm);
|
|
2445
|
+
color: var(--text-body);
|
|
2391
2446
|
font-family: inherit;
|
|
2392
|
-
font-size: var(--font-size-
|
|
2447
|
+
font-size: var(--font-size-sm);
|
|
2393
2448
|
cursor: pointer;
|
|
2394
|
-
text-align: left;
|
|
2395
|
-
box-sizing: border-box;
|
|
2396
2449
|
transition: background var(--transition-normal), border-color var(--transition-normal);
|
|
2397
2450
|
}
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
.twm-action-dropdown-option:focus {
|
|
2403
|
-
background: rgba(255, 255, 255, 0.1);
|
|
2404
|
-
border-color: rgba(255, 255, 255, 0.08);
|
|
2405
|
-
outline: none;
|
|
2406
|
-
}
|
|
2407
|
-
.twm-action-dropdown-option .twm-option-icon {
|
|
2408
|
-
font-size: 20px;
|
|
2409
|
-
color: #78b9ff;
|
|
2410
|
-
flex-shrink: 0;
|
|
2451
|
+
|
|
2452
|
+
.tile-grid-toolbar .toolbar-btn:hover {
|
|
2453
|
+
background: var(--state-hover);
|
|
2454
|
+
border-color: var(--border-strong);
|
|
2411
2455
|
}
|
|
2412
|
-
|
|
2413
|
-
.
|
|
2414
|
-
|
|
2456
|
+
|
|
2457
|
+
.tile-grid-toolbar .toolbar-btn .material-symbols-outlined {
|
|
2458
|
+
font-size: var(--font-size-xl);
|
|
2415
2459
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2460
|
+
|
|
2461
|
+
.tile-grid-toolbar .btn-text {
|
|
2418
2462
|
white-space: nowrap;
|
|
2419
|
-
overflow: hidden;
|
|
2420
|
-
text-overflow: ellipsis;
|
|
2421
2463
|
}
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
white-space: nowrap;
|
|
2426
|
-
overflow: hidden;
|
|
2427
|
-
text-overflow: ellipsis;
|
|
2464
|
+
|
|
2465
|
+
.tile-grid-toolbar .dropdown-arrow {
|
|
2466
|
+
opacity: 0.6;
|
|
2428
2467
|
}
|
|
2429
2468
|
|
|
2430
|
-
/*
|
|
2431
|
-
|
|
2432
|
-
.
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
height: 35px;
|
|
2436
|
-
background: #2a2a2a;
|
|
2437
|
-
border-bottom: 1px solid var(--surface-0);
|
|
2438
|
-
gap: var(--space-xs);
|
|
2439
|
-
flex: 0 0 auto;
|
|
2469
|
+
/* `--main` is the ONLY group `_buildToolbar()` emits, and all three buttons
|
|
2470
|
+
sit inside it, so it takes the strip's whole width — `space-between` on the
|
|
2471
|
+
toolbar has nothing to space until a consumer adds a second group. */
|
|
2472
|
+
.tile-grid-toolbar .toolbar-group--main {
|
|
2473
|
+
flex: 1;
|
|
2440
2474
|
}
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2475
|
+
|
|
2476
|
+
/* Add Widget is the toolbar's one primary verb, so it is the one that carries
|
|
2477
|
+
the accent. */
|
|
2478
|
+
.add-widget-btn {
|
|
2479
|
+
background: rgba(0, 122, 204, 0.15);
|
|
2480
|
+
border-color: rgba(0, 122, 204, 0.3);
|
|
2481
|
+
color: var(--text-bright);
|
|
2445
2482
|
}
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
align-items: center;
|
|
2451
|
-
background: transparent;
|
|
2452
|
-
color: var(--text-muted);
|
|
2453
|
-
border: none;
|
|
2454
|
-
border-radius: var(--radius-sm) 3px 0 0;
|
|
2455
|
-
cursor: pointer;
|
|
2456
|
-
font-size: var(--font-size-sm);
|
|
2457
|
-
transition: all var(--transition-normal);
|
|
2458
|
-
position: relative;
|
|
2483
|
+
|
|
2484
|
+
.add-widget-btn:hover {
|
|
2485
|
+
background: rgba(0, 122, 204, 0.25);
|
|
2486
|
+
border-color: rgba(0, 122, 204, 0.5);
|
|
2459
2487
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2488
|
+
|
|
2489
|
+
/* The other two are secondary — Export is an aside and Reset Layout throws the
|
|
2490
|
+
board away, and neither should read as the thing to press. */
|
|
2491
|
+
.export-btn,
|
|
2492
|
+
.reset-layout-btn {
|
|
2493
|
+
color: var(--text-muted);
|
|
2463
2494
|
}
|
|
2464
2495
|
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
display: flex;
|
|
2469
|
-
align-items: center;
|
|
2470
|
-
gap: var(--space-sm);
|
|
2471
|
-
height: 26px;
|
|
2472
|
-
min-width: 200px;
|
|
2473
|
-
max-width: 320px;
|
|
2474
|
-
width: 100%;
|
|
2475
|
-
padding: 0 10px;
|
|
2476
|
-
background: #2b2b2c;
|
|
2477
|
-
border: 1px solid #3a3a3b;
|
|
2478
|
-
border-radius: 5px;
|
|
2479
|
-
transition: border-color var(--transition-normal), background var(--transition-normal);
|
|
2496
|
+
.export-btn:hover,
|
|
2497
|
+
.reset-layout-btn:hover {
|
|
2498
|
+
color: var(--text-body);
|
|
2480
2499
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2500
|
+
|
|
2501
|
+
/* ── the tile ────────────────────────────────────────────────────────── */
|
|
2502
|
+
/* Flat, one 1 px border, no radius and no shadow: dense expert-tool chrome,
|
|
2503
|
+
the register of a trading terminal rather than a card wall. The donor also
|
|
2504
|
+
carried `margin: -1px 0 0 -1px` so that adjacent borders collapsed into one
|
|
2505
|
+
line — which is only true at `gap: 0`, and this grid's default gap is 16 px,
|
|
2506
|
+
where the same rule just shifts every tile a pixel up and left. Dropped on
|
|
2507
|
+
purpose; a consumer running gap-0 can add it back in one rule. */
|
|
2508
|
+
.tile {
|
|
2483
2509
|
background: var(--surface-1);
|
|
2510
|
+
border: 1px solid var(--border-default);
|
|
2511
|
+
border-radius: 0;
|
|
2512
|
+
display: flex;
|
|
2513
|
+
flex-direction: column;
|
|
2514
|
+
position: relative;
|
|
2515
|
+
overflow: hidden;
|
|
2516
|
+
height: 100%;
|
|
2517
|
+
min-height: 0;
|
|
2518
|
+
transition: border-color var(--transition-normal);
|
|
2484
2519
|
}
|
|
2485
|
-
|
|
2486
|
-
.
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
left: 50%;
|
|
2490
|
-
transform: translateX(-50%);
|
|
2491
|
-
width: 320px;
|
|
2492
|
-
max-height: 400px;
|
|
2493
|
-
overflow-y: auto;
|
|
2494
|
-
background: #2b2b2c;
|
|
2495
|
-
border: 1px solid #3a3a3b;
|
|
2496
|
-
border-radius: var(--radius-md);
|
|
2497
|
-
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
2498
|
-
z-index: 3500;
|
|
2520
|
+
|
|
2521
|
+
.tile:hover {
|
|
2522
|
+
border-color: var(--border-strong);
|
|
2523
|
+
box-shadow: none;
|
|
2499
2524
|
}
|
|
2500
|
-
|
|
2501
|
-
.
|
|
2502
|
-
|
|
2525
|
+
|
|
2526
|
+
.tile.dragging {
|
|
2527
|
+
opacity: 0.5;
|
|
2528
|
+
z-index: var(--z-dropdown);
|
|
2529
|
+
box-shadow: var(--shadow-lg);
|
|
2503
2530
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2531
|
+
|
|
2532
|
+
.tile.resizing {
|
|
2533
|
+
z-index: var(--z-dropdown);
|
|
2534
|
+
border-color: var(--accent-bright);
|
|
2535
|
+
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.3);
|
|
2506
2536
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2537
|
+
|
|
2538
|
+
/* Selected — set by `_onConfigClick()` while the config dialog is open, and
|
|
2539
|
+
cleared from every sibling by the same handler. #78b9ff is a literal
|
|
2540
|
+
because there is no light-accent token: `--accent-bright` is #007acc, which
|
|
2541
|
+
is a border colour on this background and not a text one. */
|
|
2542
|
+
.tile.tile--selected {
|
|
2543
|
+
border-color: #78b9ff;
|
|
2544
|
+
box-shadow: 0 0 0 2px rgba(120, 185, 255, 0.3), var(--shadow-md);
|
|
2510
2545
|
}
|
|
2511
|
-
|
|
2512
|
-
|
|
2546
|
+
|
|
2547
|
+
.tile.tile--selected .tile-header {
|
|
2548
|
+
background: rgba(120, 185, 255, 0.1);
|
|
2513
2549
|
}
|
|
2514
2550
|
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
*
|
|
2519
|
-
* Styles for the canvas zoom control buttons and global zoom indicator.
|
|
2520
|
-
* Buttons: positioned in the bottom-right corner of the canvas.
|
|
2521
|
-
* Indicator: positioned in the global bottom-right of the application.
|
|
2522
|
-
*/
|
|
2551
|
+
.tile.tile--selected .tile-title {
|
|
2552
|
+
color: #78b9ff;
|
|
2553
|
+
}
|
|
2523
2554
|
|
|
2524
|
-
/*
|
|
2525
|
-
|
|
2526
|
-
|
|
2555
|
+
/* Read-only. `_buildChrome()`'s read-only branch emits no drag handle, no
|
|
2556
|
+
cogwheel, no ⋯ menu, no remove button and no resize handle, so there is
|
|
2557
|
+
nothing here to hide — what is left is to stop the tile advertising the
|
|
2558
|
+
affordances it no longer has. */
|
|
2559
|
+
.tile--readonly .tile-header {
|
|
2560
|
+
cursor: default;
|
|
2527
2561
|
}
|
|
2528
2562
|
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
* Styles for draggable/resizable window component with taskbar integration.
|
|
2533
|
-
*/
|
|
2563
|
+
.tile--readonly:hover {
|
|
2564
|
+
border-color: var(--border-default);
|
|
2565
|
+
}
|
|
2534
2566
|
|
|
2535
|
-
/*
|
|
2567
|
+
/* ── the header strip ────────────────────────────────────────────────── */
|
|
2568
|
+
.tile-header {
|
|
2569
|
+
display: flex;
|
|
2570
|
+
align-items: center;
|
|
2571
|
+
gap: var(--space-xs);
|
|
2572
|
+
padding: 0 var(--space-sm);
|
|
2573
|
+
background: var(--surface-0);
|
|
2574
|
+
border-bottom: 1px solid var(--border-default);
|
|
2575
|
+
min-height: 22px;
|
|
2576
|
+
flex-shrink: 0;
|
|
2577
|
+
}
|
|
2536
2578
|
|
|
2537
|
-
.
|
|
2538
|
-
position: fixed;
|
|
2539
|
-
background: #1a1a1a;
|
|
2540
|
-
border: 1px solid var(--surface-3);
|
|
2541
|
-
border-radius: 0;
|
|
2542
|
-
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
2579
|
+
.tile-drag-handle {
|
|
2543
2580
|
display: flex;
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2581
|
+
align-items: center;
|
|
2582
|
+
justify-content: center;
|
|
2583
|
+
width: 16px;
|
|
2584
|
+
height: 18px;
|
|
2585
|
+
color: rgba(255, 255, 255, 0.3);
|
|
2586
|
+
cursor: grab;
|
|
2587
|
+
transition: color var(--transition-normal);
|
|
2550
2588
|
}
|
|
2551
|
-
|
|
2552
|
-
|
|
2589
|
+
|
|
2590
|
+
.tile-drag-handle:hover {
|
|
2591
|
+
color: rgba(255, 255, 255, 0.6);
|
|
2553
2592
|
}
|
|
2554
|
-
|
|
2555
|
-
.
|
|
2556
|
-
|
|
2557
|
-
opacity: 0;
|
|
2558
|
-
pointer-events: none;
|
|
2593
|
+
|
|
2594
|
+
.tile-drag-handle:active {
|
|
2595
|
+
cursor: grabbing;
|
|
2559
2596
|
}
|
|
2560
|
-
|
|
2561
|
-
.
|
|
2562
|
-
|
|
2597
|
+
|
|
2598
|
+
.tile-drag-handle .material-symbols-outlined {
|
|
2599
|
+
font-size: var(--font-size-lg);
|
|
2563
2600
|
}
|
|
2564
|
-
/* ========== Backdrop (for modal windows) ========== */
|
|
2565
2601
|
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2602
|
+
/* `cursor: default`, where the donor said `pointer`. In EcoAgent the title
|
|
2603
|
+
opened the config panel; in this library that moved to the cogwheel alone
|
|
2604
|
+
("Config opens ONLY via the explicit cogwheel button" — `_buildChrome`), so
|
|
2605
|
+
a pointer cursor here promises a click that does nothing. */
|
|
2606
|
+
.tile-title {
|
|
2607
|
+
flex: 1;
|
|
2608
|
+
min-width: 0;
|
|
2609
|
+
font-size: var(--font-size-xs);
|
|
2610
|
+
font-weight: var(--font-weight-semibold);
|
|
2611
|
+
color: var(--text-body);
|
|
2612
|
+
text-transform: uppercase;
|
|
2613
|
+
letter-spacing: 0.04em;
|
|
2614
|
+
white-space: nowrap;
|
|
2615
|
+
overflow: hidden;
|
|
2616
|
+
text-overflow: ellipsis;
|
|
2617
|
+
cursor: default;
|
|
2618
|
+
padding: 0;
|
|
2619
|
+
border-radius: 0;
|
|
2620
|
+
line-height: 1;
|
|
2575
2621
|
}
|
|
2576
|
-
/* Per-modal overrides of the dim/blur above are set as inline styles by
|
|
2577
|
-
ManagedWindow from its `backdropBlur` / `backdropOpacity` options — not
|
|
2578
|
-
via modifier classes here. */
|
|
2579
2622
|
|
|
2580
|
-
|
|
2623
|
+
.tile-controls {
|
|
2624
|
+
display: flex;
|
|
2625
|
+
align-items: center;
|
|
2626
|
+
gap: var(--space-xs);
|
|
2627
|
+
margin-left: var(--space-md);
|
|
2628
|
+
flex-shrink: 0;
|
|
2629
|
+
}
|
|
2581
2630
|
|
|
2582
|
-
.
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2631
|
+
/* One geometry for every icon button in the strip. They differ only in what
|
|
2632
|
+
they turn when you hover them, which is the next block. */
|
|
2633
|
+
.tile-config-btn,
|
|
2634
|
+
.tile-menu-btn,
|
|
2635
|
+
.tile-expand-btn,
|
|
2636
|
+
.tile-remove-btn {
|
|
2587
2637
|
display: flex;
|
|
2588
2638
|
align-items: center;
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2639
|
+
justify-content: center;
|
|
2640
|
+
width: 20px;
|
|
2641
|
+
height: 20px;
|
|
2642
|
+
padding: 0;
|
|
2643
|
+
background: transparent;
|
|
2644
|
+
border: none;
|
|
2645
|
+
border-radius: var(--radius-sm);
|
|
2646
|
+
color: rgba(255, 255, 255, 0.4);
|
|
2647
|
+
cursor: pointer;
|
|
2648
|
+
transition: background var(--transition-normal), color var(--transition-normal);
|
|
2593
2649
|
}
|
|
2594
|
-
|
|
2595
|
-
.
|
|
2596
|
-
|
|
2650
|
+
|
|
2651
|
+
.tile-config-btn:hover,
|
|
2652
|
+
.tile-menu-btn:hover,
|
|
2653
|
+
.tile-expand-btn:hover {
|
|
2654
|
+
background: var(--state-hover);
|
|
2655
|
+
color: #78b9ff;
|
|
2597
2656
|
}
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2657
|
+
|
|
2658
|
+
.tile-remove-btn:hover {
|
|
2659
|
+
background: var(--state-hover);
|
|
2660
|
+
color: #ff6b6b;
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
.tile-config-btn .material-symbols-outlined,
|
|
2664
|
+
.tile-menu-btn .material-symbols-outlined,
|
|
2665
|
+
.tile-expand-btn .material-symbols-outlined,
|
|
2666
|
+
.tile-remove-btn .material-symbols-outlined {
|
|
2667
|
+
font-size: var(--font-size-xl);
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
/* The ⓘ button carries its own inline `style` from `_buildChrome`, and inline
|
|
2671
|
+
style beats any rule in this file — background, border, padding and colour
|
|
2672
|
+
are already decided there and CANNOT be changed from here. So this rule adds
|
|
2673
|
+
only what the inline style leaves open. A hover background written here
|
|
2674
|
+
would be a rule that never applies: exactly the dead rule the header comment
|
|
2675
|
+
warns about, arrived at from the other direction. */
|
|
2676
|
+
.tile-info-btn {
|
|
2677
|
+
width: 20px;
|
|
2678
|
+
height: 20px;
|
|
2679
|
+
border-radius: var(--radius-sm);
|
|
2601
2680
|
flex-shrink: 0;
|
|
2602
2681
|
}
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
text-overflow: ellipsis;
|
|
2610
|
-
white-space: nowrap;
|
|
2682
|
+
|
|
2683
|
+
/* ── the ⋯ menu ──────────────────────────────────────────────────────── */
|
|
2684
|
+
/* Rendered from `TileBase.menuItems()`; an empty list emits no wrapper at all,
|
|
2685
|
+
so none of this draws for a consumer that overrides the hook to `[]`. */
|
|
2686
|
+
.tile-menu-wrapper {
|
|
2687
|
+
position: relative;
|
|
2611
2688
|
}
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2689
|
+
|
|
2690
|
+
.tile-menu-dropdown {
|
|
2691
|
+
position: absolute;
|
|
2692
|
+
top: 100%;
|
|
2693
|
+
right: 0;
|
|
2694
|
+
z-index: var(--z-dropdown);
|
|
2695
|
+
min-width: 180px;
|
|
2696
|
+
padding: var(--space-xs) 0;
|
|
2697
|
+
background: var(--surface-2);
|
|
2698
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
2699
|
+
border-radius: var(--radius-md);
|
|
2700
|
+
box-shadow: var(--shadow-md);
|
|
2616
2701
|
}
|
|
2617
|
-
|
|
2618
|
-
/*
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2702
|
+
|
|
2703
|
+
/* `!important`, matching `.twm-action-dropdown-menu[hidden]` below and for the
|
|
2704
|
+
same reason: `hidden` is the JS's only way of closing this menu
|
|
2705
|
+
(`menuDropdown.hidden = !menuDropdown.hidden`), and the UA implements the
|
|
2706
|
+
attribute as the weakest possible rule — any consumer selector that sets a
|
|
2707
|
+
display on the dropdown would leave it open with nothing to close it. */
|
|
2708
|
+
.tile-menu-dropdown[hidden] {
|
|
2709
|
+
display: none !important;
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
.tile-menu-item {
|
|
2628
2713
|
display: flex;
|
|
2629
2714
|
align-items: center;
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
box-shadow: none;
|
|
2634
|
-
transition: background-color 0.1s, color 0.1s;
|
|
2635
|
-
}
|
|
2636
|
-
/* Focus state - no outline */
|
|
2637
|
-
button.twm-managed-window__btn:focus,
|
|
2638
|
-
button.twm-managed-window__btn:focus-visible {
|
|
2639
|
-
outline: none;
|
|
2640
|
-
box-shadow: none;
|
|
2715
|
+
gap: var(--space-md);
|
|
2716
|
+
width: 100%;
|
|
2717
|
+
padding: var(--space-sm) var(--space-lg);
|
|
2641
2718
|
background: none;
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
/* Active state */
|
|
2651
|
-
button.twm-managed-window__btn:active {
|
|
2652
|
-
background: #4a4a4a;
|
|
2653
|
-
background-color: #4a4a4a;
|
|
2719
|
+
border: none;
|
|
2720
|
+
color: var(--text-secondary);
|
|
2721
|
+
font-family: inherit;
|
|
2722
|
+
font-size: var(--font-size-sm);
|
|
2723
|
+
text-align: left;
|
|
2724
|
+
cursor: pointer;
|
|
2725
|
+
white-space: nowrap;
|
|
2726
|
+
transition: background var(--transition-fast);
|
|
2654
2727
|
}
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
background:
|
|
2658
|
-
background-color: #c42b1c;
|
|
2728
|
+
|
|
2729
|
+
.tile-menu-item:hover {
|
|
2730
|
+
background: var(--state-active);
|
|
2659
2731
|
color: var(--text-bright);
|
|
2660
2732
|
}
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2733
|
+
|
|
2734
|
+
.tile-menu-item .material-symbols-outlined {
|
|
2735
|
+
font-size: var(--font-size-xl);
|
|
2736
|
+
color: rgba(255, 255, 255, 0.5);
|
|
2664
2737
|
}
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
.twm-managed-window .twm-managed-window__btn svg rect,
|
|
2669
|
-
button.twm-managed-window__btn svg path,
|
|
2670
|
-
button.twm-managed-window__btn svg rect {
|
|
2671
|
-
stroke: var(--text-bright);
|
|
2672
|
-
stroke-width: 1.5;
|
|
2738
|
+
|
|
2739
|
+
.tile-menu-item:hover .material-symbols-outlined {
|
|
2740
|
+
color: #78b9ff;
|
|
2673
2741
|
}
|
|
2674
|
-
/* ========== Content Area ========== */
|
|
2675
2742
|
|
|
2676
|
-
|
|
2743
|
+
/* ── the content area ────────────────────────────────────────────────── */
|
|
2744
|
+
/* `min-height: 0` is the load-bearing declaration, not the padding: a flex
|
|
2745
|
+
child's default `min-height: auto` refuses to shrink below its content, so
|
|
2746
|
+
without it a tall table or a chart pushes the tile past its grid rows
|
|
2747
|
+
instead of scrolling inside them. */
|
|
2748
|
+
.tile-content {
|
|
2677
2749
|
flex: 1;
|
|
2750
|
+
padding: var(--space-lg);
|
|
2678
2751
|
overflow: hidden;
|
|
2679
2752
|
position: relative;
|
|
2753
|
+
min-height: 0;
|
|
2680
2754
|
display: flex;
|
|
2681
2755
|
flex-direction: column;
|
|
2682
2756
|
}
|
|
2683
|
-
/* ========== Resize Handles ========== */
|
|
2684
2757
|
|
|
2685
|
-
|
|
2758
|
+
/* A chart library that measures its container on draw gets a container with a
|
|
2759
|
+
real size. The `!important` is the only lever there is: Plotly writes width
|
|
2760
|
+
and height INLINE onto `.js-plotly-plot` when it draws, an inline
|
|
2761
|
+
declaration outranks any selector, and `!important` is what outranks it —
|
|
2762
|
+
without which a chart keeps the size it had when the tile was half as wide. */
|
|
2763
|
+
.tile-content > [class*="-container"] {
|
|
2764
|
+
flex: 1;
|
|
2765
|
+
min-height: 0;
|
|
2766
|
+
width: 100%;
|
|
2767
|
+
position: relative;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
.tile-content .js-plotly-plot,
|
|
2771
|
+
.tile-content .plot-container.plotly,
|
|
2772
|
+
.tile-content .svg-container {
|
|
2773
|
+
width: 100% !important;
|
|
2774
|
+
height: 100% !important;
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
/* ── resize handle and drag ghost ────────────────────────────────────── */
|
|
2778
|
+
.tile-resize-handle {
|
|
2686
2779
|
position: absolute;
|
|
2780
|
+
background: transparent;
|
|
2687
2781
|
z-index: var(--z-sticky);
|
|
2688
2782
|
}
|
|
2689
|
-
/* ========== Taskbar in Bottom Bar ========== */
|
|
2690
2783
|
|
|
2691
|
-
.
|
|
2784
|
+
.tile-resize-handle.se {
|
|
2785
|
+
bottom: 0;
|
|
2786
|
+
right: 0;
|
|
2787
|
+
width: 24px;
|
|
2788
|
+
height: 24px;
|
|
2789
|
+
cursor: se-resize;
|
|
2692
2790
|
display: flex;
|
|
2693
2791
|
align-items: center;
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
}
|
|
2698
|
-
.twm-bar-windows:empty {
|
|
2699
|
-
display: none;
|
|
2792
|
+
justify-content: center;
|
|
2793
|
+
color: rgba(255, 255, 255, 0.2);
|
|
2794
|
+
transition: color var(--transition-normal);
|
|
2700
2795
|
}
|
|
2701
|
-
|
|
2702
|
-
.
|
|
2703
|
-
|
|
2704
|
-
height: 100%;
|
|
2705
|
-
background: var(--surface-3);
|
|
2796
|
+
|
|
2797
|
+
.tile-resize-handle.se:hover {
|
|
2798
|
+
color: rgba(255, 255, 255, 0.5);
|
|
2706
2799
|
}
|
|
2707
|
-
|
|
2708
|
-
|
|
2800
|
+
|
|
2801
|
+
.tile-resize-handle .material-symbols-outlined {
|
|
2802
|
+
font-size: var(--font-size-xl);
|
|
2803
|
+
transform: rotate(-45deg);
|
|
2709
2804
|
}
|
|
2710
|
-
|
|
2711
|
-
|
|
2805
|
+
|
|
2806
|
+
/* The drop preview, positioned by `_updateGhost()` in grid coordinates. */
|
|
2807
|
+
.tile-ghost {
|
|
2808
|
+
position: absolute;
|
|
2809
|
+
background: rgba(0, 122, 204, 0.15);
|
|
2810
|
+
border: 2px dashed rgba(0, 122, 204, 0.5);
|
|
2811
|
+
border-radius: var(--radius-sm);
|
|
2812
|
+
pointer-events: none;
|
|
2813
|
+
z-index: 50;
|
|
2814
|
+
transition: transform 0.1s ease;
|
|
2712
2815
|
}
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
border-radius: 0;
|
|
2719
|
-
font-size: var(--font-size-xs);
|
|
2720
|
-
font-family: inherit;
|
|
2721
|
-
color: var(--text-secondary);
|
|
2722
|
-
cursor: pointer;
|
|
2723
|
-
max-width: 160px;
|
|
2816
|
+
|
|
2817
|
+
/* ── the three content states ────────────────────────────────────────── */
|
|
2818
|
+
/* `showLoading()`, `showEmpty()` and `showError()` each replace the whole of
|
|
2819
|
+
`.tile-content`, so all three centre themselves in it. */
|
|
2820
|
+
.tile-loading {
|
|
2724
2821
|
display: flex;
|
|
2822
|
+
flex-direction: column;
|
|
2725
2823
|
align-items: center;
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
/* Solarized blue */
|
|
2731
|
-
background: #268bd2;
|
|
2732
|
-
color: var(--text-bright);
|
|
2733
|
-
}
|
|
2734
|
-
.twm-bar-windows__item:active {
|
|
2735
|
-
background: #1a6aa5;
|
|
2736
|
-
}
|
|
2737
|
-
.twm-bar-windows__item-icon {
|
|
2824
|
+
justify-content: center;
|
|
2825
|
+
height: 100%;
|
|
2826
|
+
gap: var(--space-md);
|
|
2827
|
+
color: rgba(255, 255, 255, 0.5);
|
|
2738
2828
|
font-size: var(--font-size-sm);
|
|
2739
|
-
flex-shrink: 0;
|
|
2740
2829
|
}
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
white-space: nowrap;
|
|
2830
|
+
|
|
2831
|
+
.tile-loading .material-symbols-outlined {
|
|
2832
|
+
font-size: 24px;
|
|
2745
2833
|
}
|
|
2746
|
-
/* ========== Transitions ========== */
|
|
2747
2834
|
|
|
2748
|
-
|
|
2749
|
-
|
|
2835
|
+
/* Scoped, because `.spinning` on its own is a name a consumer is entitled to
|
|
2836
|
+
use for its own spinner. `spin` is not scopeable — `@keyframes` names are
|
|
2837
|
+
global — and a consumer that defines its own `spin` later in the cascade
|
|
2838
|
+
simply spins this icon its way, which is harmless. */
|
|
2839
|
+
.tile-loading .spinning {
|
|
2840
|
+
animation: spin 1s linear infinite;
|
|
2750
2841
|
}
|
|
2751
|
-
/* ========== Expression Modal Content ========== */
|
|
2752
2842
|
|
|
2753
|
-
|
|
2843
|
+
@keyframes spin {
|
|
2844
|
+
from { transform: rotate(0deg); }
|
|
2845
|
+
to { transform: rotate(360deg); }
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
.tile-empty {
|
|
2754
2849
|
display: flex;
|
|
2755
2850
|
flex-direction: column;
|
|
2851
|
+
align-items: center;
|
|
2852
|
+
justify-content: center;
|
|
2756
2853
|
height: 100%;
|
|
2757
|
-
|
|
2854
|
+
gap: var(--space-md);
|
|
2855
|
+
color: rgba(255, 255, 255, 0.4);
|
|
2856
|
+
font-size: var(--font-size-sm);
|
|
2857
|
+
text-align: center;
|
|
2758
2858
|
}
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
font-size:
|
|
2762
|
-
|
|
2763
|
-
border-bottom: 1px solid #333;
|
|
2764
|
-
flex-shrink: 0;
|
|
2859
|
+
|
|
2860
|
+
.tile-empty .material-symbols-outlined {
|
|
2861
|
+
font-size: 32px;
|
|
2862
|
+
opacity: 0.5;
|
|
2765
2863
|
}
|
|
2766
|
-
.twm-expression-modal__window-content .twm-expression-modal__editor-container {
|
|
2767
|
-
flex: 1;
|
|
2768
|
-
overflow: hidden;
|
|
2769
|
-
display: flex;
|
|
2770
|
-
flex-direction: column;
|
|
2771
|
-
padding: 5px;
|
|
2772
|
-
}
|
|
2773
|
-
/* ========== Confirm Dialog Content ========== */
|
|
2774
2864
|
|
|
2775
|
-
.
|
|
2865
|
+
/* THE ERROR STATE IS PREFIXED AND THE OTHER TWO ARE NOT. `showError()` emits
|
|
2866
|
+
`.twm-tile-error` where `showEmpty()` emits `.tile-empty` — an asymmetry
|
|
2867
|
+
that is now a fixed fact of the wire, because this rule shipped before the
|
|
2868
|
+
rest of the block did. The donor's unprefixed `.tile-error` is deliberately
|
|
2869
|
+
NOT ported: it would sit here looking correct and matching nothing. */
|
|
2870
|
+
.twm-tile-error {
|
|
2776
2871
|
display: flex;
|
|
2777
2872
|
flex-direction: column;
|
|
2873
|
+
align-items: center;
|
|
2874
|
+
justify-content: center;
|
|
2778
2875
|
height: 100%;
|
|
2779
|
-
padding: var(--space-xl);
|
|
2780
|
-
box-sizing: border-box;
|
|
2781
|
-
}
|
|
2782
|
-
.twm-confirm-dialog__body {
|
|
2783
|
-
flex: 1;
|
|
2784
|
-
display: flex;
|
|
2785
|
-
align-items: flex-start;
|
|
2786
|
-
gap: var(--space-lg);
|
|
2787
|
-
}
|
|
2788
|
-
.twm-confirm-dialog__icon {
|
|
2789
|
-
font-size: 32px;
|
|
2790
|
-
color: var(--text-muted);
|
|
2791
|
-
flex-shrink: 0;
|
|
2792
|
-
}
|
|
2793
|
-
.twm-confirm-dialog__message {
|
|
2794
|
-
flex: 1;
|
|
2795
|
-
font-size: var(--font-size-md);
|
|
2796
|
-
color: var(--text-secondary);
|
|
2797
|
-
line-height: 1.5;
|
|
2798
|
-
white-space: pre-wrap;
|
|
2799
|
-
padding-top: var(--space-xs);
|
|
2800
|
-
}
|
|
2801
|
-
.twm-confirm-dialog__actions {
|
|
2802
|
-
display: flex;
|
|
2803
|
-
justify-content: flex-end;
|
|
2804
2876
|
gap: var(--space-md);
|
|
2805
|
-
|
|
2806
|
-
padding-top: var(--space-lg);
|
|
2807
|
-
border-top: 1px solid #333;
|
|
2808
|
-
}
|
|
2809
|
-
/* Confirm dialog buttons - override global button styles */
|
|
2810
|
-
button.twm-confirm-dialog__btn {
|
|
2811
|
-
padding: var(--space-sm) 16px;
|
|
2812
|
-
border: 1px solid #444;
|
|
2813
|
-
border-radius: var(--radius-sm);
|
|
2877
|
+
color: #ff6b6b;
|
|
2814
2878
|
font-size: var(--font-size-sm);
|
|
2815
|
-
|
|
2816
|
-
cursor: pointer;
|
|
2817
|
-
transition: background-color var(--transition-normal), border-color var(--transition-normal);
|
|
2879
|
+
text-align: center;
|
|
2818
2880
|
}
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2881
|
+
|
|
2882
|
+
.twm-tile-error .material-symbols-outlined {
|
|
2883
|
+
font-size: 32px;
|
|
2822
2884
|
}
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2885
|
+
/* ===================================================================
|
|
2886
|
+
REUSABLE ACTION DROPDOWN COMPONENT
|
|
2887
|
+
Used by Add Scenario, Add Widget, and other action dropdowns
|
|
2888
|
+
=================================================================== */
|
|
2889
|
+
|
|
2890
|
+
.twm-action-dropdown-menu {
|
|
2891
|
+
/* position/top/left set by ActionDropdown.position() via inline style */
|
|
2892
|
+
position: fixed;
|
|
2893
|
+
min-width: 220px;
|
|
2894
|
+
padding: var(--space-md);
|
|
2895
|
+
background: linear-gradient(145deg, rgba(30, 32, 38, 0.98), rgba(24, 26, 32, 0.96));
|
|
2896
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
2897
|
+
border-radius: 10px;
|
|
2898
|
+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);
|
|
2899
|
+
z-index: var(--z-toast);
|
|
2900
|
+
opacity: 0;
|
|
2901
|
+
pointer-events: none;
|
|
2902
|
+
transition: opacity 0.16s ease;
|
|
2826
2903
|
}
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
outline-offset: 2px;
|
|
2904
|
+
.twm-action-dropdown-menu[hidden] {
|
|
2905
|
+
display: none !important;
|
|
2830
2906
|
}
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
/**
|
|
2834
|
-
* About Dialog Styles
|
|
2835
|
-
* Modal dialog showing EcoSim version, license, and third-party credits.
|
|
2836
|
-
*/
|
|
2837
|
-
|
|
2838
|
-
/* ========== Root Container ========== */
|
|
2839
|
-
|
|
2840
|
-
.twm-about-dialog__content {
|
|
2907
|
+
/* Dropdown options */
|
|
2908
|
+
.twm-action-dropdown-option {
|
|
2841
2909
|
display: flex;
|
|
2842
|
-
|
|
2843
|
-
|
|
2910
|
+
align-items: center;
|
|
2911
|
+
gap: 10px;
|
|
2912
|
+
width: 100%;
|
|
2913
|
+
padding: 10px 12px;
|
|
2914
|
+
margin-bottom: var(--space-xs);
|
|
2915
|
+
background: rgba(255, 255, 255, 0.03);
|
|
2916
|
+
border: 1px solid transparent;
|
|
2917
|
+
border-radius: var(--radius-md);
|
|
2918
|
+
color: rgba(255, 255, 255, 0.85);
|
|
2919
|
+
font-family: inherit;
|
|
2920
|
+
font-size: var(--font-size-md);
|
|
2921
|
+
cursor: pointer;
|
|
2922
|
+
text-align: left;
|
|
2844
2923
|
box-sizing: border-box;
|
|
2924
|
+
transition: background var(--transition-normal), border-color var(--transition-normal);
|
|
2845
2925
|
}
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
.twm-about-dialog__header {
|
|
2849
|
-
display: flex;
|
|
2850
|
-
flex-direction: column;
|
|
2851
|
-
align-items: center;
|
|
2852
|
-
gap: var(--space-xs);
|
|
2853
|
-
padding: 20px 16px 16px;
|
|
2854
|
-
border-bottom: 1px solid #333;
|
|
2855
|
-
flex-shrink: 0;
|
|
2926
|
+
.twm-action-dropdown-option:last-child {
|
|
2927
|
+
margin-bottom: 0;
|
|
2856
2928
|
}
|
|
2857
|
-
.twm-
|
|
2858
|
-
|
|
2859
|
-
|
|
2929
|
+
.twm-action-dropdown-option:hover,
|
|
2930
|
+
.twm-action-dropdown-option:focus {
|
|
2931
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2932
|
+
border-color: rgba(255, 255, 255, 0.08);
|
|
2933
|
+
outline: none;
|
|
2860
2934
|
}
|
|
2861
|
-
.twm-
|
|
2935
|
+
.twm-action-dropdown-option .twm-option-icon {
|
|
2862
2936
|
font-size: 20px;
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
margin: 0;
|
|
2937
|
+
color: #78b9ff;
|
|
2938
|
+
flex-shrink: 0;
|
|
2866
2939
|
}
|
|
2867
|
-
.twm-
|
|
2868
|
-
|
|
2869
|
-
color:
|
|
2940
|
+
.twm-action-dropdown-option:hover .twm-option-icon,
|
|
2941
|
+
.twm-action-dropdown-option:focus .twm-option-icon {
|
|
2942
|
+
color: #9ecfff;
|
|
2870
2943
|
}
|
|
2871
|
-
.twm-
|
|
2872
|
-
font-
|
|
2873
|
-
|
|
2944
|
+
.twm-action-dropdown-option .twm-option-label {
|
|
2945
|
+
font-weight: 500;
|
|
2946
|
+
white-space: nowrap;
|
|
2947
|
+
overflow: hidden;
|
|
2948
|
+
text-overflow: ellipsis;
|
|
2874
2949
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
.
|
|
2878
|
-
|
|
2879
|
-
overflow
|
|
2880
|
-
|
|
2881
|
-
min-height: 0;
|
|
2950
|
+
.twm-action-dropdown-option .twm-option-desc {
|
|
2951
|
+
font-size: var(--font-size-xs);
|
|
2952
|
+
color: rgba(255, 255, 255, 0.5);
|
|
2953
|
+
white-space: nowrap;
|
|
2954
|
+
overflow: hidden;
|
|
2955
|
+
text-overflow: ellipsis;
|
|
2882
2956
|
}
|
|
2883
|
-
/* ========== Sections ========== */
|
|
2884
2957
|
|
|
2885
|
-
.
|
|
2886
|
-
|
|
2958
|
+
/* ── from code.css ───────────────────────── */
|
|
2959
|
+
/* Code language tabs */
|
|
2960
|
+
.twm-code-tabs-header {
|
|
2961
|
+
display: flex;
|
|
2962
|
+
align-items: center;
|
|
2963
|
+
height: 35px;
|
|
2964
|
+
background: #2a2a2a;
|
|
2965
|
+
border-bottom: 1px solid var(--surface-0);
|
|
2966
|
+
gap: var(--space-xs);
|
|
2967
|
+
flex: 0 0 auto;
|
|
2887
2968
|
}
|
|
2888
|
-
.twm-
|
|
2889
|
-
|
|
2969
|
+
.twm-code-tabs {
|
|
2970
|
+
display: flex;
|
|
2971
|
+
gap: var(--space-xxs);
|
|
2972
|
+
height: 35px;
|
|
2890
2973
|
}
|
|
2891
|
-
.twm-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2974
|
+
.twm-code-tab {
|
|
2975
|
+
height: 35px;
|
|
2976
|
+
padding: 0 12px;
|
|
2977
|
+
display: flex;
|
|
2978
|
+
align-items: center;
|
|
2979
|
+
background: transparent;
|
|
2980
|
+
color: var(--text-muted);
|
|
2981
|
+
border: none;
|
|
2982
|
+
border-radius: var(--radius-sm) 3px 0 0;
|
|
2983
|
+
cursor: pointer;
|
|
2984
|
+
font-size: var(--font-size-sm);
|
|
2985
|
+
transition: all var(--transition-normal);
|
|
2986
|
+
position: relative;
|
|
2987
|
+
}
|
|
2988
|
+
.twm-code-tab:hover {
|
|
2989
|
+
background: #333;
|
|
2990
|
+
color: #ccc;
|
|
2898
2991
|
}
|
|
2899
|
-
/* ========== Project Links ========== */
|
|
2900
2992
|
|
|
2901
|
-
.
|
|
2993
|
+
/* ── from global_search.css ───────────────────────── */
|
|
2994
|
+
/* Search wrapper - contains icon, input, hint */
|
|
2995
|
+
.twm-global-search-wrapper {
|
|
2902
2996
|
display: flex;
|
|
2903
|
-
flex-direction: column;
|
|
2904
|
-
gap: var(--space-sm);
|
|
2905
|
-
}
|
|
2906
|
-
.twm-about-dialog__link {
|
|
2907
|
-
display: inline-flex;
|
|
2908
2997
|
align-items: center;
|
|
2909
2998
|
gap: var(--space-sm);
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2999
|
+
height: 26px;
|
|
3000
|
+
min-width: 200px;
|
|
3001
|
+
max-width: 320px;
|
|
3002
|
+
width: 100%;
|
|
3003
|
+
padding: 0 10px;
|
|
3004
|
+
background: #2b2b2c;
|
|
3005
|
+
border: 1px solid #3a3a3b;
|
|
3006
|
+
border-radius: 5px;
|
|
3007
|
+
transition: border-color var(--transition-normal), background var(--transition-normal);
|
|
2914
3008
|
}
|
|
2915
|
-
.twm-
|
|
2916
|
-
|
|
2917
|
-
|
|
3009
|
+
.twm-global-search-wrapper:focus-within {
|
|
3010
|
+
border-color: var(--accent-bright);
|
|
3011
|
+
background: var(--surface-1);
|
|
2918
3012
|
}
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
3013
|
+
/* Dropdown container */
|
|
3014
|
+
.twm-global-search-dropdown {
|
|
3015
|
+
position: absolute;
|
|
3016
|
+
top: calc(100% + 4px);
|
|
3017
|
+
left: 50%;
|
|
3018
|
+
transform: translateX(-50%);
|
|
3019
|
+
width: 320px;
|
|
3020
|
+
max-height: 400px;
|
|
3021
|
+
overflow-y: auto;
|
|
3022
|
+
background: #2b2b2c;
|
|
3023
|
+
border: 1px solid #3a3a3b;
|
|
3024
|
+
border-radius: var(--radius-md);
|
|
3025
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
3026
|
+
z-index: 3500;
|
|
2925
3027
|
}
|
|
2926
|
-
/*
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
display: flex;
|
|
2930
|
-
flex-direction: column;
|
|
2931
|
-
gap: var(--space-xs);
|
|
3028
|
+
/* Scrollbar styling for dropdown */
|
|
3029
|
+
.twm-global-search-dropdown::-webkit-scrollbar {
|
|
3030
|
+
width: 6px;
|
|
2932
3031
|
}
|
|
2933
|
-
.twm-
|
|
2934
|
-
|
|
2935
|
-
align-items: baseline;
|
|
2936
|
-
gap: var(--space-md);
|
|
2937
|
-
padding: var(--space-xxs) 0;
|
|
3032
|
+
.twm-global-search-dropdown::-webkit-scrollbar-track {
|
|
3033
|
+
background: transparent;
|
|
2938
3034
|
}
|
|
2939
|
-
.twm-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
text-decoration: none;
|
|
2943
|
-
cursor: pointer;
|
|
2944
|
-
flex-shrink: 0;
|
|
3035
|
+
.twm-global-search-dropdown::-webkit-scrollbar-thumb {
|
|
3036
|
+
background: #4a4a4a;
|
|
3037
|
+
border-radius: var(--radius-sm);
|
|
2945
3038
|
}
|
|
2946
|
-
.twm-
|
|
2947
|
-
|
|
2948
|
-
color: #4da6ff;
|
|
3039
|
+
.twm-global-search-dropdown::-webkit-scrollbar-thumb:hover {
|
|
3040
|
+
background: #5a5a5a;
|
|
2949
3041
|
}
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
3042
|
+
|
|
3043
|
+
/* ── from zoom_controls.css ───────────────────────── */
|
|
3044
|
+
/**
|
|
3045
|
+
* Zoom Controls Styles
|
|
3046
|
+
*
|
|
3047
|
+
* Styles for the canvas zoom control buttons and global zoom indicator.
|
|
3048
|
+
* Buttons: positioned in the bottom-right corner of the canvas.
|
|
3049
|
+
* Indicator: positioned in the global bottom-right of the application.
|
|
3050
|
+
*/
|
|
3051
|
+
|
|
3052
|
+
/* Ensure .twm-left-top is a positioning context for the zoom controls */
|
|
3053
|
+
.twm-left-top {
|
|
3054
|
+
position: relative;
|
|
2954
3055
|
}
|
|
2955
|
-
/* ========== Footer ========== */
|
|
2956
3056
|
|
|
2957
|
-
.
|
|
3057
|
+
/* ── from managed_window.css ───────────────────────── */
|
|
3058
|
+
/**
|
|
3059
|
+
* Managed Window Styles
|
|
3060
|
+
* Styles for draggable/resizable window component with taskbar integration.
|
|
3061
|
+
*/
|
|
3062
|
+
|
|
3063
|
+
/* ========== Window Container ========== */
|
|
3064
|
+
|
|
3065
|
+
.twm-managed-window {
|
|
3066
|
+
position: fixed;
|
|
3067
|
+
background: #1a1a1a;
|
|
3068
|
+
border: 1px solid var(--surface-3);
|
|
3069
|
+
border-radius: 0;
|
|
3070
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
2958
3071
|
display: flex;
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
3072
|
+
flex-direction: column;
|
|
3073
|
+
overflow: hidden;
|
|
3074
|
+
min-width: 200px;
|
|
3075
|
+
min-height: 100px;
|
|
3076
|
+
/* Enable transitions for minimize/maximize animations */
|
|
3077
|
+
transition: transform 0.2s ease-out, opacity 0.2s ease-out, box-shadow var(--transition-normal);
|
|
2963
3078
|
}
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
3079
|
+
/* C3. Containment. `--contained` swaps `position: fixed` for `absolute` so the
|
|
3080
|
+
* window is placed relative to its container, which must be a containing block
|
|
3081
|
+
* established with `position: relative`/`absolute` and NOTHING ELSE.
|
|
3082
|
+
*
|
|
3083
|
+
* This is the most dangerous rule in the containment work. `transform`,
|
|
3084
|
+
* `filter`, `contain` and `will-change` on the container ALSO create a
|
|
3085
|
+
* containing block — and they additionally capture `position: fixed`
|
|
3086
|
+
* descendants. DataTable's filter dropdown (`position: fixed; z-index: 10001`)
|
|
3087
|
+
* and the autocomplete dropdown (`z-index: 10000000`) deliberately escape their
|
|
3088
|
+
* tile to the viewport; under a transformed ancestor they become
|
|
3089
|
+
* container-relative and are clipped by the container's `overflow: hidden`.
|
|
3090
|
+
* The symptom is "the filter dropdown is cut in half" and the cause is three
|
|
3091
|
+
* files away. */
|
|
3092
|
+
.twm-managed-window--contained {
|
|
3093
|
+
position: absolute;
|
|
2972
3094
|
}
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3095
|
+
.twm-managed-window__backdrop--contained {
|
|
3096
|
+
position: absolute;
|
|
3097
|
+
top: 0;
|
|
3098
|
+
left: 0;
|
|
3099
|
+
right: 0;
|
|
3100
|
+
bottom: 0;
|
|
2977
3101
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
3102
|
+
/* The layer a contained window is mounted into. `position: relative` and
|
|
3103
|
+
* `overflow: hidden` ONLY — see the warning above. */
|
|
3104
|
+
.tbl-window-layer {
|
|
3105
|
+
position: absolute;
|
|
3106
|
+
inset: 0;
|
|
3107
|
+
overflow: hidden;
|
|
2981
3108
|
}
|
|
2982
|
-
button.
|
|
2983
|
-
|
|
2984
|
-
|
|
3109
|
+
/* C6. An extra titlebar button (the table cogwheel), sized like the built-ins. */
|
|
3110
|
+
.twm-managed-window__btn--custom .material-symbols-outlined {
|
|
3111
|
+
font-size: 13px;
|
|
3112
|
+
line-height: 1;
|
|
3113
|
+
}
|
|
3114
|
+
.twm-managed-window:focus-within {
|
|
3115
|
+
border-color: #505050;
|
|
3116
|
+
}
|
|
3117
|
+
/* Minimize animation class — targets taskbar item via CSS custom properties */
|
|
3118
|
+
.twm-managed-window--minimizing {
|
|
3119
|
+
transform: translate(var(--mw-target-x, 0px), var(--mw-target-y, 100vh)) scale(var(--mw-target-scale, 0.1));
|
|
3120
|
+
opacity: 0;
|
|
3121
|
+
pointer-events: none;
|
|
3122
|
+
}
|
|
3123
|
+
/* Restore animation class — animates from taskbar item position */
|
|
3124
|
+
.twm-managed-window--restoring {
|
|
3125
|
+
animation: windowRestore 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
3126
|
+
}
|
|
3127
|
+
/* C27. THE KEYFRAMES THE RULE ABOVE HAS ALWAYS NAMED AND NOTHING EVER DEFINED.
|
|
3128
|
+
`animation: windowRestore ...` referred to an `@keyframes` block that does
|
|
3129
|
+
not exist anywhere in either stylesheet, which CSS treats as no animation at
|
|
3130
|
+
all — so the restore was an instant pop while its mirror image, the minimise,
|
|
3131
|
+
played for 200ms. The fifth instance of this codebase's signature defect: one
|
|
3132
|
+
side names a thing the other side has never heard of, nothing throws, and no
|
|
3133
|
+
check in any of the three consumers can see it. Deliberately the exact
|
|
3134
|
+
inverse of `--minimizing`, from the same three custom properties, so the two
|
|
3135
|
+
halves of the gesture cannot describe different geometry. */
|
|
3136
|
+
@keyframes windowRestore {
|
|
3137
|
+
from {
|
|
3138
|
+
transform: translate(var(--mw-target-x, 0px), var(--mw-target-y, 100vh))
|
|
3139
|
+
scale(var(--mw-target-scale, 0.1));
|
|
3140
|
+
opacity: 0;
|
|
3141
|
+
}
|
|
3142
|
+
to {
|
|
3143
|
+
transform: none;
|
|
3144
|
+
opacity: 1;
|
|
3145
|
+
}
|
|
2985
3146
|
}
|
|
3147
|
+
/* ========== Backdrop (for modal windows) ========== */
|
|
2986
3148
|
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
3149
|
+
.twm-managed-window__backdrop {
|
|
3150
|
+
position: fixed;
|
|
3151
|
+
/* Position between top bar (35px) and bottom bar (20px) */
|
|
3152
|
+
top: 35px;
|
|
3153
|
+
left: 0;
|
|
3154
|
+
right: 0;
|
|
3155
|
+
bottom: 20px;
|
|
3156
|
+
background: rgba(0, 0, 0, 0.5);
|
|
3157
|
+
backdrop-filter: blur(2px);
|
|
3158
|
+
}
|
|
3159
|
+
/* Per-modal overrides of the dim/blur above are set as inline styles by
|
|
3160
|
+
ManagedWindow from its `backdropBlur` / `backdropOpacity` options — not
|
|
3161
|
+
via modifier classes here. */
|
|
2992
3162
|
|
|
2993
|
-
|
|
3163
|
+
/* ========== Top Bar ========== */
|
|
3164
|
+
|
|
3165
|
+
.twm-managed-window__topbar {
|
|
3166
|
+
height: 28px;
|
|
3167
|
+
min-height: 28px;
|
|
3168
|
+
background: var(--surface-1);
|
|
3169
|
+
border-bottom: 1px solid var(--surface-3);
|
|
2994
3170
|
display: flex;
|
|
2995
3171
|
align-items: center;
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3172
|
+
padding: 0 8px;
|
|
3173
|
+
cursor: move;
|
|
3174
|
+
user-select: none;
|
|
3175
|
+
gap: var(--space-sm);
|
|
3000
3176
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
gap: 10px;
|
|
3005
|
-
min-width: 0;
|
|
3177
|
+
/* Non-draggable windows (modals) */
|
|
3178
|
+
.twm-managed-window--no-drag .twm-managed-window__topbar {
|
|
3179
|
+
cursor: default;
|
|
3006
3180
|
}
|
|
3007
|
-
.twm-
|
|
3008
|
-
font-size:
|
|
3009
|
-
color: var(--text-muted
|
|
3181
|
+
.twm-managed-window__icon {
|
|
3182
|
+
font-size: var(--font-size-lg);
|
|
3183
|
+
color: var(--text-muted);
|
|
3010
3184
|
flex-shrink: 0;
|
|
3011
3185
|
}
|
|
3012
|
-
.twm-
|
|
3013
|
-
|
|
3014
|
-
font-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
white-space: nowrap;
|
|
3186
|
+
.twm-managed-window__title {
|
|
3187
|
+
flex: 1;
|
|
3188
|
+
font-size: var(--font-size-sm);
|
|
3189
|
+
font-weight: 500;
|
|
3190
|
+
color: var(--text-secondary);
|
|
3018
3191
|
overflow: hidden;
|
|
3019
3192
|
text-overflow: ellipsis;
|
|
3020
|
-
|
|
3193
|
+
white-space: nowrap;
|
|
3021
3194
|
}
|
|
3022
|
-
.twm-
|
|
3023
|
-
|
|
3195
|
+
.twm-managed-window__buttons {
|
|
3196
|
+
display: flex;
|
|
3197
|
+
gap: var(--space-xxs);
|
|
3198
|
+
margin-left: auto;
|
|
3024
3199
|
}
|
|
3025
|
-
|
|
3200
|
+
/* Window buttons - white icons on transparent background */
|
|
3201
|
+
/* Use button element + class selector to override global "button" styles from main_new.css */
|
|
3202
|
+
button.twm-managed-window__btn {
|
|
3203
|
+
width: 24px;
|
|
3204
|
+
height: 20px;
|
|
3205
|
+
border: none;
|
|
3206
|
+
background: none;
|
|
3207
|
+
background-color: transparent;
|
|
3026
3208
|
color: var(--text-bright);
|
|
3209
|
+
cursor: pointer;
|
|
3210
|
+
border-radius: 0;
|
|
3211
|
+
display: flex;
|
|
3212
|
+
align-items: center;
|
|
3213
|
+
justify-content: center;
|
|
3214
|
+
padding: 0;
|
|
3215
|
+
outline: none;
|
|
3216
|
+
box-shadow: none;
|
|
3217
|
+
transition: background-color 0.1s, color 0.1s;
|
|
3027
3218
|
}
|
|
3028
|
-
/*
|
|
3029
|
-
.twm-
|
|
3030
|
-
|
|
3031
|
-
font-size: var(--font-size-2xl);
|
|
3032
|
-
font-weight: 600;
|
|
3033
|
-
background: var(--surface-1));
|
|
3034
|
-
border: 1px solid var(--accent);
|
|
3035
|
-
border-radius: var(--radius-sm);
|
|
3036
|
-
color: var(--text-primary, #f0f0f0);
|
|
3037
|
-
padding: 0 4px;
|
|
3038
|
-
margin: -2px 0;
|
|
3219
|
+
/* Focus state - no outline */
|
|
3220
|
+
button.twm-managed-window__btn:focus,
|
|
3221
|
+
button.twm-managed-window__btn:focus-visible {
|
|
3039
3222
|
outline: none;
|
|
3040
|
-
|
|
3041
|
-
|
|
3223
|
+
box-shadow: none;
|
|
3224
|
+
background: none;
|
|
3225
|
+
background-color: transparent;
|
|
3042
3226
|
}
|
|
3043
|
-
/*
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
.twm-detail-header__subtitle {
|
|
3050
|
-
width: 100%;
|
|
3051
|
-
font-size: var(--font-size-sm);
|
|
3052
|
-
color: var(--text-muted, var(--text-muted));
|
|
3053
|
-
margin-top: -8px;
|
|
3054
|
-
padding-left: 30px;
|
|
3227
|
+
/* Hover state - dark gray background */
|
|
3228
|
+
button.twm-managed-window__btn:hover {
|
|
3229
|
+
background: var(--surface-3);
|
|
3230
|
+
background-color: var(--surface-3);
|
|
3231
|
+
color: var(--text-bright);
|
|
3055
3232
|
}
|
|
3056
|
-
|
|
3057
|
-
|
|
3233
|
+
/* Active state */
|
|
3234
|
+
button.twm-managed-window__btn:active {
|
|
3235
|
+
background: #4a4a4a;
|
|
3236
|
+
background-color: #4a4a4a;
|
|
3058
3237
|
}
|
|
3059
|
-
/*
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
gap: var(--space-sm);
|
|
3065
|
-
flex-shrink: 0;
|
|
3238
|
+
/* Close button hover - red */
|
|
3239
|
+
button.twm-managed-window__btn--close:hover {
|
|
3240
|
+
background: #c42b1c;
|
|
3241
|
+
background-color: #c42b1c;
|
|
3242
|
+
color: var(--text-bright);
|
|
3066
3243
|
}
|
|
3067
|
-
|
|
3068
|
-
|
|
3244
|
+
.twm-managed-window__btn svg {
|
|
3245
|
+
width: 10px;
|
|
3246
|
+
height: 10px;
|
|
3247
|
+
}
|
|
3248
|
+
/* Override global path stroke from main_new.css which sets stroke: #2c3e50 on all paths */
|
|
3249
|
+
/* Need high specificity to beat: path:not(.connection-path):not(.connection-hitbox) = (0,2,1) */
|
|
3250
|
+
.twm-managed-window .twm-managed-window__btn svg path,
|
|
3251
|
+
.twm-managed-window .twm-managed-window__btn svg rect,
|
|
3252
|
+
button.twm-managed-window__btn svg path,
|
|
3253
|
+
button.twm-managed-window__btn svg rect {
|
|
3254
|
+
stroke: var(--text-bright);
|
|
3255
|
+
stroke-width: 1.5;
|
|
3256
|
+
}
|
|
3257
|
+
/* ========== Content Area ========== */
|
|
3258
|
+
|
|
3259
|
+
.twm-managed-window__content {
|
|
3260
|
+
flex: 1;
|
|
3261
|
+
overflow: hidden;
|
|
3262
|
+
position: relative;
|
|
3263
|
+
display: flex;
|
|
3264
|
+
flex-direction: column;
|
|
3265
|
+
}
|
|
3266
|
+
/* ========== Resize Handles ========== */
|
|
3267
|
+
|
|
3268
|
+
.twm-managed-window__resize {
|
|
3269
|
+
position: absolute;
|
|
3270
|
+
z-index: var(--z-sticky);
|
|
3271
|
+
}
|
|
3272
|
+
/* ========== Taskbar in Bottom Bar ========== */
|
|
3273
|
+
|
|
3274
|
+
.twm-bar-windows {
|
|
3275
|
+
display: flex;
|
|
3276
|
+
align-items: center;
|
|
3277
|
+
gap: var(--space-xs);
|
|
3278
|
+
padding: 0 4px;
|
|
3279
|
+
height: 100%;
|
|
3280
|
+
}
|
|
3281
|
+
.twm-bar-windows:empty {
|
|
3282
|
+
display: none;
|
|
3283
|
+
}
|
|
3284
|
+
/* Taskbar dividers */
|
|
3285
|
+
.twm-bar-windows__divider {
|
|
3286
|
+
width: 1px;
|
|
3287
|
+
height: 100%;
|
|
3288
|
+
background: var(--surface-3);
|
|
3289
|
+
}
|
|
3290
|
+
.twm-bar-windows__divider--left {
|
|
3291
|
+
margin-right: var(--space-xs);
|
|
3292
|
+
}
|
|
3293
|
+
.twm-bar-windows__divider--right {
|
|
3294
|
+
margin-left: var(--space-xs);
|
|
3295
|
+
}
|
|
3296
|
+
.twm-bar-windows__item {
|
|
3297
|
+
height: 100%;
|
|
3298
|
+
padding: 0 6px;
|
|
3299
|
+
background: transparent;
|
|
3300
|
+
border: none;
|
|
3301
|
+
border-radius: 0;
|
|
3302
|
+
font-size: var(--font-size-xs);
|
|
3303
|
+
font-family: inherit;
|
|
3304
|
+
color: var(--text-secondary);
|
|
3305
|
+
cursor: pointer;
|
|
3306
|
+
max-width: 160px;
|
|
3307
|
+
display: flex;
|
|
3308
|
+
align-items: center;
|
|
3309
|
+
gap: var(--space-xs);
|
|
3310
|
+
transition: background-color 0.1s, color 0.1s;
|
|
3311
|
+
}
|
|
3312
|
+
.twm-bar-windows__item:hover {
|
|
3313
|
+
/* Solarized blue */
|
|
3314
|
+
background: #268bd2;
|
|
3315
|
+
color: var(--text-bright);
|
|
3316
|
+
}
|
|
3317
|
+
.twm-bar-windows__item:active {
|
|
3318
|
+
background: #1a6aa5;
|
|
3319
|
+
}
|
|
3320
|
+
.twm-bar-windows__item-icon {
|
|
3321
|
+
font-size: var(--font-size-sm);
|
|
3322
|
+
flex-shrink: 0;
|
|
3323
|
+
}
|
|
3324
|
+
.twm-bar-windows__item-title {
|
|
3325
|
+
overflow: hidden;
|
|
3326
|
+
text-overflow: ellipsis;
|
|
3327
|
+
white-space: nowrap;
|
|
3328
|
+
}
|
|
3329
|
+
/* ========== Transitions ========== */
|
|
3330
|
+
|
|
3331
|
+
.twm-managed-window:not(:hover) {
|
|
3332
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
|
3333
|
+
}
|
|
3334
|
+
/* ========== Expression Modal Content ========== */
|
|
3335
|
+
|
|
3336
|
+
.twm-expression-modal__window-content {
|
|
3337
|
+
display: flex;
|
|
3338
|
+
flex-direction: column;
|
|
3339
|
+
height: 100%;
|
|
3340
|
+
overflow: hidden;
|
|
3341
|
+
}
|
|
3342
|
+
.twm-expression-modal__window-content .twm-expression-modal__meta {
|
|
3343
|
+
padding: var(--space-md) 12px;
|
|
3344
|
+
font-size: var(--font-size-xs);
|
|
3345
|
+
color: var(--text-muted);
|
|
3346
|
+
border-bottom: 1px solid #333;
|
|
3347
|
+
flex-shrink: 0;
|
|
3348
|
+
}
|
|
3349
|
+
.twm-expression-modal__window-content .twm-expression-modal__editor-container {
|
|
3350
|
+
flex: 1;
|
|
3351
|
+
overflow: hidden;
|
|
3352
|
+
display: flex;
|
|
3353
|
+
flex-direction: column;
|
|
3354
|
+
padding: 5px;
|
|
3355
|
+
}
|
|
3356
|
+
/* ========== Confirm Dialog Content ========== */
|
|
3357
|
+
|
|
3358
|
+
.twm-confirm-dialog__content {
|
|
3359
|
+
display: flex;
|
|
3360
|
+
flex-direction: column;
|
|
3361
|
+
height: 100%;
|
|
3362
|
+
padding: var(--space-xl);
|
|
3363
|
+
box-sizing: border-box;
|
|
3364
|
+
}
|
|
3365
|
+
.twm-confirm-dialog__body {
|
|
3366
|
+
flex: 1;
|
|
3367
|
+
display: flex;
|
|
3368
|
+
align-items: flex-start;
|
|
3369
|
+
gap: var(--space-lg);
|
|
3370
|
+
}
|
|
3371
|
+
.twm-confirm-dialog__icon {
|
|
3372
|
+
font-size: 32px;
|
|
3373
|
+
color: var(--text-muted);
|
|
3374
|
+
flex-shrink: 0;
|
|
3375
|
+
}
|
|
3376
|
+
.twm-confirm-dialog__message {
|
|
3377
|
+
flex: 1;
|
|
3378
|
+
font-size: var(--font-size-md);
|
|
3379
|
+
color: var(--text-secondary);
|
|
3380
|
+
line-height: 1.5;
|
|
3381
|
+
white-space: pre-wrap;
|
|
3382
|
+
padding-top: var(--space-xs);
|
|
3383
|
+
}
|
|
3384
|
+
.twm-confirm-dialog__actions {
|
|
3385
|
+
display: flex;
|
|
3386
|
+
justify-content: flex-end;
|
|
3387
|
+
gap: var(--space-md);
|
|
3388
|
+
margin-top: var(--space-xl);
|
|
3389
|
+
padding-top: var(--space-lg);
|
|
3390
|
+
border-top: 1px solid #333;
|
|
3391
|
+
}
|
|
3392
|
+
/* Confirm dialog buttons - override global button styles */
|
|
3393
|
+
button.twm-confirm-dialog__btn {
|
|
3394
|
+
padding: var(--space-sm) 16px;
|
|
3395
|
+
border: 1px solid #444;
|
|
3396
|
+
border-radius: var(--radius-sm);
|
|
3397
|
+
font-size: var(--font-size-sm);
|
|
3398
|
+
font-family: inherit;
|
|
3399
|
+
cursor: pointer;
|
|
3400
|
+
transition: background-color var(--transition-normal), border-color var(--transition-normal);
|
|
3401
|
+
}
|
|
3402
|
+
button.twm-confirm-dialog__btn--primary {
|
|
3403
|
+
background: var(--accent);
|
|
3404
|
+
border-color: var(--accent);
|
|
3405
|
+
}
|
|
3406
|
+
button.twm-confirm-dialog__btn--primary:hover {
|
|
3407
|
+
background: var(--accent-hover);
|
|
3408
|
+
border-color: var(--accent-hover);
|
|
3409
|
+
}
|
|
3410
|
+
button.twm-confirm-dialog__btn:focus {
|
|
3411
|
+
outline: 2px solid var(--accent-bright);
|
|
3412
|
+
outline-offset: 2px;
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
/* ── from about_dialog.css ───────────────────────── */
|
|
3416
|
+
/**
|
|
3417
|
+
* About Dialog Styles
|
|
3418
|
+
* Modal dialog showing EcoSim version, license, and third-party credits.
|
|
3419
|
+
*/
|
|
3420
|
+
|
|
3421
|
+
/* ========== Root Container ========== */
|
|
3422
|
+
|
|
3423
|
+
.twm-about-dialog__content {
|
|
3424
|
+
display: flex;
|
|
3425
|
+
flex-direction: column;
|
|
3426
|
+
height: 100%;
|
|
3427
|
+
box-sizing: border-box;
|
|
3428
|
+
}
|
|
3429
|
+
/* ========== Header ========== */
|
|
3430
|
+
|
|
3431
|
+
.twm-about-dialog__header {
|
|
3432
|
+
display: flex;
|
|
3433
|
+
flex-direction: column;
|
|
3434
|
+
align-items: center;
|
|
3435
|
+
gap: var(--space-xs);
|
|
3436
|
+
padding: 20px 16px 16px;
|
|
3437
|
+
border-bottom: 1px solid #333;
|
|
3438
|
+
flex-shrink: 0;
|
|
3439
|
+
}
|
|
3440
|
+
.twm-about-dialog__logo {
|
|
3441
|
+
font-size: 40px;
|
|
3442
|
+
color: var(--accent-bright);
|
|
3443
|
+
}
|
|
3444
|
+
.twm-about-dialog__title {
|
|
3445
|
+
font-size: 20px;
|
|
3446
|
+
font-weight: 600;
|
|
3447
|
+
color: var(--text-secondary);
|
|
3448
|
+
margin: 0;
|
|
3449
|
+
}
|
|
3450
|
+
.twm-about-dialog__version {
|
|
3451
|
+
font-size: var(--font-size-sm);
|
|
3452
|
+
color: var(--text-muted);
|
|
3453
|
+
}
|
|
3454
|
+
.twm-about-dialog__tagline {
|
|
3455
|
+
font-size: var(--font-size-sm);
|
|
3456
|
+
color: var(--text-muted);
|
|
3457
|
+
}
|
|
3458
|
+
/* ========== Scrollable Body ========== */
|
|
3459
|
+
|
|
3460
|
+
.twm-about-dialog__body {
|
|
3461
|
+
flex: 1;
|
|
3462
|
+
overflow-y: auto;
|
|
3463
|
+
padding: var(--space-lg) 16px;
|
|
3464
|
+
min-height: 0;
|
|
3465
|
+
}
|
|
3466
|
+
/* ========== Sections ========== */
|
|
3467
|
+
|
|
3468
|
+
.twm-about-dialog__section {
|
|
3469
|
+
margin-bottom: 14px;
|
|
3470
|
+
}
|
|
3471
|
+
.twm-about-dialog__section:last-child {
|
|
3472
|
+
margin-bottom: 0;
|
|
3473
|
+
}
|
|
3474
|
+
.twm-about-dialog__section-title {
|
|
3475
|
+
font-size: var(--font-size-xs);
|
|
3476
|
+
font-weight: 600;
|
|
3477
|
+
color: var(--text-muted);
|
|
3478
|
+
text-transform: uppercase;
|
|
3479
|
+
letter-spacing: 0.06em;
|
|
3480
|
+
margin-bottom: var(--space-md);
|
|
3481
|
+
}
|
|
3482
|
+
/* ========== Project Links ========== */
|
|
3483
|
+
|
|
3484
|
+
.twm-about-dialog__links {
|
|
3485
|
+
display: flex;
|
|
3486
|
+
flex-direction: column;
|
|
3487
|
+
gap: var(--space-sm);
|
|
3488
|
+
}
|
|
3489
|
+
.twm-about-dialog__link {
|
|
3490
|
+
display: inline-flex;
|
|
3491
|
+
align-items: center;
|
|
3492
|
+
gap: var(--space-sm);
|
|
3493
|
+
font-size: var(--font-size-sm);
|
|
3494
|
+
color: #3794ff;
|
|
3495
|
+
text-decoration: none;
|
|
3496
|
+
cursor: pointer;
|
|
3497
|
+
}
|
|
3498
|
+
.twm-about-dialog__link:hover {
|
|
3499
|
+
text-decoration: underline;
|
|
3500
|
+
color: #4da6ff;
|
|
3501
|
+
}
|
|
3502
|
+
.twm-about-dialog__license-text {
|
|
3503
|
+
font-size: var(--font-size-sm);
|
|
3504
|
+
color: var(--text-muted);
|
|
3505
|
+
display: inline-flex;
|
|
3506
|
+
align-items: center;
|
|
3507
|
+
gap: var(--space-sm);
|
|
3508
|
+
}
|
|
3509
|
+
/* ========== Dependency List ========== */
|
|
3510
|
+
|
|
3511
|
+
.twm-about-dialog__dep-list {
|
|
3512
|
+
display: flex;
|
|
3513
|
+
flex-direction: column;
|
|
3514
|
+
gap: var(--space-xs);
|
|
3515
|
+
}
|
|
3516
|
+
.twm-about-dialog__dep {
|
|
3517
|
+
display: flex;
|
|
3518
|
+
align-items: baseline;
|
|
3519
|
+
gap: var(--space-md);
|
|
3520
|
+
padding: var(--space-xxs) 0;
|
|
3521
|
+
}
|
|
3522
|
+
.twm-about-dialog__dep-name {
|
|
3523
|
+
font-size: var(--font-size-sm);
|
|
3524
|
+
color: #3794ff;
|
|
3525
|
+
text-decoration: none;
|
|
3526
|
+
cursor: pointer;
|
|
3527
|
+
flex-shrink: 0;
|
|
3528
|
+
}
|
|
3529
|
+
.twm-about-dialog__dep-name:hover {
|
|
3530
|
+
text-decoration: underline;
|
|
3531
|
+
color: #4da6ff;
|
|
3532
|
+
}
|
|
3533
|
+
.twm-about-dialog__dep-license {
|
|
3534
|
+
font-size: var(--font-size-xs);
|
|
3535
|
+
color: var(--text-dim);
|
|
3536
|
+
flex-shrink: 0;
|
|
3537
|
+
}
|
|
3538
|
+
/* ========== Footer ========== */
|
|
3539
|
+
|
|
3540
|
+
.twm-about-dialog__footer {
|
|
3541
|
+
display: flex;
|
|
3542
|
+
justify-content: flex-end;
|
|
3543
|
+
padding: 10px 16px;
|
|
3544
|
+
border-top: 1px solid #333;
|
|
3545
|
+
flex-shrink: 0;
|
|
3546
|
+
}
|
|
3547
|
+
button.twm-about-dialog__btn {
|
|
3548
|
+
padding: var(--space-sm) 16px;
|
|
3549
|
+
border: 1px solid #444;
|
|
3550
|
+
border-radius: var(--radius-sm);
|
|
3551
|
+
font-size: var(--font-size-sm);
|
|
3552
|
+
font-family: inherit;
|
|
3553
|
+
cursor: pointer;
|
|
3554
|
+
transition: background-color var(--transition-normal), border-color var(--transition-normal);
|
|
3555
|
+
}
|
|
3556
|
+
button.twm-about-dialog__btn--close {
|
|
3557
|
+
background: var(--accent);
|
|
3558
|
+
border-color: var(--accent);
|
|
3559
|
+
color: var(--text-bright);
|
|
3560
|
+
}
|
|
3561
|
+
button.twm-about-dialog__btn--close:hover {
|
|
3562
|
+
background: var(--accent-hover);
|
|
3563
|
+
border-color: var(--accent-hover);
|
|
3564
|
+
}
|
|
3565
|
+
button.twm-about-dialog__btn--close:focus {
|
|
3566
|
+
outline: 2px solid var(--accent-bright);
|
|
3567
|
+
outline-offset: 2px;
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
/* ── from components/detail_header.css ───────────────────────── */
|
|
3571
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
3572
|
+
DetailHeader — shared header bar for detail views across all pages.
|
|
3573
|
+
Used by: Scenario page, ETL page, Data page, Notebook editors.
|
|
3574
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
3575
|
+
|
|
3576
|
+
.twm-detail-header {
|
|
3577
|
+
display: flex;
|
|
3578
|
+
align-items: center;
|
|
3579
|
+
justify-content: space-between;
|
|
3580
|
+
gap: var(--space-xl);
|
|
3581
|
+
padding-bottom: var(--space-lg);
|
|
3582
|
+
flex-wrap: wrap;
|
|
3583
|
+
}
|
|
3584
|
+
.twm-detail-header__title-row {
|
|
3585
|
+
display: flex;
|
|
3586
|
+
align-items: center;
|
|
3587
|
+
gap: 10px;
|
|
3588
|
+
min-width: 0;
|
|
3589
|
+
}
|
|
3590
|
+
.twm-detail-header__icon {
|
|
3591
|
+
font-size: 20px;
|
|
3592
|
+
color: var(--text-muted, var(--text-muted));
|
|
3593
|
+
flex-shrink: 0;
|
|
3594
|
+
}
|
|
3595
|
+
.twm-detail-header__name {
|
|
3596
|
+
font-size: var(--font-size-2xl);
|
|
3597
|
+
font-weight: 600;
|
|
3598
|
+
color: var(--text-primary, #f0f0f0);
|
|
3599
|
+
margin: 0;
|
|
3600
|
+
white-space: nowrap;
|
|
3601
|
+
overflow: hidden;
|
|
3602
|
+
text-overflow: ellipsis;
|
|
3603
|
+
cursor: default;
|
|
3604
|
+
}
|
|
3605
|
+
.twm-detail-header__name[data-renameable] {
|
|
3606
|
+
cursor: text;
|
|
3607
|
+
}
|
|
3608
|
+
.twm-detail-header__name[data-renameable]:hover {
|
|
3609
|
+
color: var(--text-bright);
|
|
3610
|
+
}
|
|
3611
|
+
/* Inline rename input (injected by InlineRenamer) */
|
|
3612
|
+
.twm-detail-header__name .twm-node-edit-input {
|
|
3613
|
+
font: inherit;
|
|
3614
|
+
font-size: var(--font-size-2xl);
|
|
3615
|
+
font-weight: 600;
|
|
3616
|
+
background: var(--surface-1));
|
|
3617
|
+
border: 1px solid var(--accent);
|
|
3618
|
+
border-radius: var(--radius-sm);
|
|
3619
|
+
color: var(--text-primary, #f0f0f0);
|
|
3620
|
+
padding: 0 4px;
|
|
3621
|
+
margin: -2px 0;
|
|
3622
|
+
outline: none;
|
|
3623
|
+
width: 100%;
|
|
3624
|
+
min-width: 100px;
|
|
3625
|
+
}
|
|
3626
|
+
/* ─── Badges ──────────────────────────────────────────────────────────── */
|
|
3627
|
+
|
|
3628
|
+
/* .detail-header__badge* — moved to components/controls.css (Phase 2). */
|
|
3629
|
+
|
|
3630
|
+
/* ─── Subtitle ────────────────────────────────────────────────────────── */
|
|
3631
|
+
|
|
3632
|
+
.twm-detail-header__subtitle {
|
|
3633
|
+
width: 100%;
|
|
3634
|
+
font-size: var(--font-size-sm);
|
|
3635
|
+
color: var(--text-muted, var(--text-muted));
|
|
3636
|
+
margin-top: -8px;
|
|
3637
|
+
padding-left: 30px;
|
|
3638
|
+
}
|
|
3639
|
+
.twm-detail-header__subtitle strong {
|
|
3640
|
+
color: var(--text-primary, var(--text-primary));
|
|
3641
|
+
}
|
|
3642
|
+
/* ─── Actions ─────────────────────────────────────────────────────────── */
|
|
3643
|
+
|
|
3644
|
+
.twm-detail-header__actions {
|
|
3645
|
+
display: flex;
|
|
3646
|
+
align-items: center;
|
|
3647
|
+
gap: var(--space-sm);
|
|
3648
|
+
flex-shrink: 0;
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
/* ── from components/attribute_list_editor.css ───────────────────────── */
|
|
3069
3652
|
/* attribute_list_editor.css — generic AttributeSpec list editor.
|
|
3070
3653
|
*
|
|
3071
|
-
* The component sets `--cols` on the host element based on the
|
|
3072
|
-
* `showColumns` spec — header strip and row cells share the same
|
|
3073
|
-
* grid template so labels stay aligned with their inputs. Each
|
|
3074
|
-
* cell uses the standard dark-theme tokens; the editor never owns
|
|
3075
|
-
* row chrome (that's SortableList's job).
|
|
3654
|
+
* The component sets `--cols` on the host element based on the
|
|
3655
|
+
* `showColumns` spec — header strip and row cells share the same
|
|
3656
|
+
* grid template so labels stay aligned with their inputs. Each
|
|
3657
|
+
* cell uses the standard dark-theme tokens; the editor never owns
|
|
3658
|
+
* row chrome (that's SortableList's job).
|
|
3659
|
+
*/
|
|
3660
|
+
|
|
3661
|
+
.twm-attr-editor {
|
|
3662
|
+
display: flex;
|
|
3663
|
+
flex-direction: column;
|
|
3664
|
+
gap: 0.25em;
|
|
3665
|
+
margin-top: 0.25em;
|
|
3666
|
+
}
|
|
3667
|
+
/* Header strip now sits inside a SortableList row chrome so the
|
|
3668
|
+
* `[handle][content][remove]` flex layout matches each data row
|
|
3669
|
+
* exactly — no more hardcoded `padding: 0 2em` that drifted as soon
|
|
3670
|
+
* as space-md or the icon size changed. The placeholder spans
|
|
3671
|
+
* (`.twm-attr-editor__header-placeholder`) reserve the handle + remove
|
|
3672
|
+
* gutters; the inner `__headers` div is the column-grid that aligns
|
|
3673
|
+
* with each row's `.twm-attr-editor__row` grid. */
|
|
3674
|
+
.twm-attr-editor__header-row {
|
|
3675
|
+
background: transparent;
|
|
3676
|
+
border: 1px solid transparent; /* keep height parity with data rows */
|
|
3677
|
+
cursor: default;
|
|
3678
|
+
}
|
|
3679
|
+
.twm-attr-editor__header-row:hover {
|
|
3680
|
+
border-color: transparent;
|
|
3681
|
+
}
|
|
3682
|
+
.twm-attr-editor__header-placeholder {
|
|
3683
|
+
visibility: hidden;
|
|
3684
|
+
pointer-events: none;
|
|
3685
|
+
}
|
|
3686
|
+
.twm-attr-editor__headers {
|
|
3687
|
+
display: grid;
|
|
3688
|
+
grid-template-columns: var(--cols, 1fr);
|
|
3689
|
+
gap: 0.4em;
|
|
3690
|
+
font-size: 0.75em;
|
|
3691
|
+
text-transform: uppercase;
|
|
3692
|
+
letter-spacing: 0.05em;
|
|
3693
|
+
color: var(--ea-text-muted, #888);
|
|
3694
|
+
}
|
|
3695
|
+
.twm-attr-editor__row {
|
|
3696
|
+
display: grid;
|
|
3697
|
+
grid-template-columns: var(--cols, 1fr);
|
|
3698
|
+
gap: 0.4em;
|
|
3699
|
+
align-items: center;
|
|
3700
|
+
/* Overridden rows append `__attention` + `__revert` siblings. We
|
|
3701
|
+
* pull those out of the grid flow (absolute) so the cells keep
|
|
3702
|
+
* their column tracks regardless of override state — otherwise the
|
|
3703
|
+
* grid auto-creates implicit tracks for the extras and shifts every
|
|
3704
|
+
* column. `position: relative` anchors the absolute children. */
|
|
3705
|
+
position: relative;
|
|
3706
|
+
}
|
|
3707
|
+
.twm-attr-editor__cell {
|
|
3708
|
+
background: var(--ea-input-bg, #1e1e1e);
|
|
3709
|
+
color: var(--ea-text, #ddd);
|
|
3710
|
+
border: 1px solid var(--ea-border, #444);
|
|
3711
|
+
padding: 0.25em 0.4em;
|
|
3712
|
+
font: inherit;
|
|
3713
|
+
box-sizing: border-box;
|
|
3714
|
+
min-width: 0;
|
|
3715
|
+
width: 100%;
|
|
3716
|
+
}
|
|
3717
|
+
/* Native <select> defaults render with light-grey chrome on most
|
|
3718
|
+
* browsers even when the background is set. Force the dark palette
|
|
3719
|
+
* + suppress the OS arrow so the type cell matches the surrounding
|
|
3720
|
+
* dark inputs in both closed AND row-tinted states. */
|
|
3721
|
+
.twm-attr-editor select.twm-attr-editor__cell {
|
|
3722
|
+
background-color: var(--ea-input-bg, #1e1e1e);
|
|
3723
|
+
color: var(--ea-text, #ddd);
|
|
3724
|
+
-webkit-appearance: none;
|
|
3725
|
+
-moz-appearance: none;
|
|
3726
|
+
appearance: none;
|
|
3727
|
+
background-image: linear-gradient(45deg, transparent 50%, var(--ea-text-muted, #888) 50%),
|
|
3728
|
+
linear-gradient(135deg, var(--ea-text-muted, #888) 50%, transparent 50%);
|
|
3729
|
+
background-position: calc(100% - 14px) 50%, calc(100% - 10px) 50%;
|
|
3730
|
+
background-size: 4px 4px, 4px 4px;
|
|
3731
|
+
background-repeat: no-repeat;
|
|
3732
|
+
padding-right: 1.5em;
|
|
3733
|
+
}
|
|
3734
|
+
.twm-attr-editor select.twm-attr-editor__cell option {
|
|
3735
|
+
background: var(--ea-input-bg, #1e1e1e);
|
|
3736
|
+
color: var(--ea-text, #ddd);
|
|
3737
|
+
}
|
|
3738
|
+
.twm-attr-editor select.twm-attr-editor__cell optgroup {
|
|
3739
|
+
background: var(--ea-bg-chrome, #161616);
|
|
3740
|
+
color: var(--ea-text-muted, #888);
|
|
3741
|
+
font-style: normal;
|
|
3742
|
+
}
|
|
3743
|
+
.twm-attr-editor__cell--readonly {
|
|
3744
|
+
background: transparent;
|
|
3745
|
+
color: var(--ea-text-muted, #888);
|
|
3746
|
+
font-family: monospace;
|
|
3747
|
+
font-size: 0.85em;
|
|
3748
|
+
border: 1px dashed var(--ea-border, #2a2a2a);
|
|
3749
|
+
}
|
|
3750
|
+
.twm-attr-editor__row input[type="checkbox"].twm-attr-editor__cell {
|
|
3751
|
+
width: auto;
|
|
3752
|
+
justify-self: start;
|
|
3753
|
+
}
|
|
3754
|
+
/* Layer 8.J1 — inheritance state. Each row gets one of three modifiers:
|
|
3755
|
+
*
|
|
3756
|
+
* --inherited: value matches the parent archetype's default. The
|
|
3757
|
+
* row gets a light blue tint AND each input cell tinted
|
|
3758
|
+
* the same so the signal reads against the dark theme.
|
|
3759
|
+
* --overridden: value differs from the parent archetype's default.
|
|
3760
|
+
* Solarized orange tint + attention dot on the left +
|
|
3761
|
+
* revert button on the right.
|
|
3762
|
+
* --neutral: no parent archetype defines this attribute. Default
|
|
3763
|
+
* styling.
|
|
3764
|
+
*
|
|
3765
|
+
* Tinting the inputs (not just the row container) is what makes the
|
|
3766
|
+
* signal actually visible in the dark theme — a 0.10 alpha background
|
|
3767
|
+
* behind dark inputs is invisible. */
|
|
3768
|
+
|
|
3769
|
+
.twm-attr-editor__row {
|
|
3770
|
+
padding: 0.15em 0.25em;
|
|
3771
|
+
border-left: 3px solid transparent;
|
|
3772
|
+
border-radius: 2px;
|
|
3773
|
+
transition: background 120ms, border-color 120ms;
|
|
3774
|
+
}
|
|
3775
|
+
/* Inherited / matching state — same green as the Public API card's
|
|
3776
|
+
* "matches archetype" rows, so the colour grammar reads consistently
|
|
3777
|
+
* across attributes and methods: green = matches the archetype,
|
|
3778
|
+
* orange = overrides, neutral = no parent involved. */
|
|
3779
|
+
.twm-attr-editor__row--inherited {
|
|
3780
|
+
background: rgba(96, 192, 96, 0.15);
|
|
3781
|
+
border-left-color: #60c060;
|
|
3782
|
+
}
|
|
3783
|
+
.twm-attr-editor__row--inherited input.twm-attr-editor__cell {
|
|
3784
|
+
background: rgba(96, 192, 96, 0.08);
|
|
3785
|
+
border-color: rgba(96, 192, 96, 0.40);
|
|
3786
|
+
}
|
|
3787
|
+
.twm-attr-editor__row--inherited select.twm-attr-editor__cell {
|
|
3788
|
+
/* Native <select> can't render a semi-transparent background
|
|
3789
|
+
* reliably (the OS popup picks up the literal colour as its
|
|
3790
|
+
* backing). Keep the dark base, signal "inherited" via border
|
|
3791
|
+
* accent only. */
|
|
3792
|
+
background-color: var(--ea-input-bg, #1e1e1e);
|
|
3793
|
+
border-color: rgba(96, 192, 96, 0.60);
|
|
3794
|
+
}
|
|
3795
|
+
.twm-attr-editor__row--overridden {
|
|
3796
|
+
background: rgba(204, 120, 65, 0.22);
|
|
3797
|
+
border-left-color: #d18138;
|
|
3798
|
+
}
|
|
3799
|
+
.twm-attr-editor__row--overridden input.twm-attr-editor__cell {
|
|
3800
|
+
background: rgba(204, 120, 65, 0.10);
|
|
3801
|
+
border-color: rgba(209, 129, 56, 0.50);
|
|
3802
|
+
color: #f0c189;
|
|
3803
|
+
}
|
|
3804
|
+
.twm-attr-editor__row--overridden select.twm-attr-editor__cell {
|
|
3805
|
+
background-color: var(--ea-input-bg, #1e1e1e);
|
|
3806
|
+
border-color: rgba(209, 129, 56, 0.60);
|
|
3807
|
+
color: #f0c189;
|
|
3808
|
+
}
|
|
3809
|
+
/* R7 — variation-override flag treatments. These additively layer on
|
|
3810
|
+
* top of the inheritance grammar (above), so an `optional` inherited
|
|
3811
|
+
* row keeps its green background AND gets the optional border accent.
|
|
3812
|
+
*
|
|
3813
|
+
* disabled : the archetype declared the slot but turned it off. The
|
|
3814
|
+
* row is greyed out to make clear it's not carried into
|
|
3815
|
+
* descendants.
|
|
3816
|
+
* forced : variations can't override this slot. Cells appear
|
|
3817
|
+
* non-editable; the lock icon (added by JS) signals why.
|
|
3818
|
+
* optional : the value is a suggestion. Green border, NO fill — so
|
|
3819
|
+
* it's visually distinct from `inherited` (green fill).
|
|
3820
|
+
* Variations may override without warning.
|
|
3076
3821
|
*/
|
|
3822
|
+
.twm-attr-editor__row--disabled {
|
|
3823
|
+
opacity: 0.55;
|
|
3824
|
+
border-left-color: var(--ea-text-dim, #777);
|
|
3825
|
+
}
|
|
3826
|
+
.twm-attr-editor__row--forced {
|
|
3827
|
+
border-left-color: #b48be0; /* soft purple = locked */
|
|
3828
|
+
}
|
|
3829
|
+
.twm-attr-editor__row--forced input.twm-attr-editor__cell,
|
|
3830
|
+
.twm-attr-editor__row--forced select.twm-attr-editor__cell {
|
|
3831
|
+
background-color: rgba(180, 139, 224, 0.08);
|
|
3832
|
+
border-color: rgba(180, 139, 224, 0.40);
|
|
3833
|
+
}
|
|
3834
|
+
.twm-attr-editor__row--optional {
|
|
3835
|
+
border-left-color: #60c060; /* same green as inherited, ... */
|
|
3836
|
+
background: transparent; /* ...but no fill — that's the cue */
|
|
3837
|
+
}
|
|
3838
|
+
.twm-attr-editor__row--optional input.twm-attr-editor__cell,
|
|
3839
|
+
.twm-attr-editor__row--optional select.twm-attr-editor__cell {
|
|
3840
|
+
background-color: var(--ea-input-bg, #1e1e1e);
|
|
3841
|
+
border-color: rgba(96, 192, 96, 0.50);
|
|
3842
|
+
}
|
|
3843
|
+
.twm-attr-editor__attention {
|
|
3844
|
+
/* Positioned in the row's left gutter so it doesn't push the
|
|
3845
|
+
* cells. The row already has `position: relative`. */
|
|
3846
|
+
position: absolute;
|
|
3847
|
+
left: -14px;
|
|
3848
|
+
top: 50%;
|
|
3849
|
+
transform: translateY(-50%);
|
|
3850
|
+
color: #d18138; /* solarized orange */
|
|
3851
|
+
font-size: 0.85em;
|
|
3852
|
+
pointer-events: none;
|
|
3853
|
+
}
|
|
3854
|
+
.twm-attr-editor__revert {
|
|
3855
|
+
/* Floats over the right edge of the row, outside the column grid.
|
|
3856
|
+
* Sits ABOVE the trailing remove-button gutter that sortable_list
|
|
3857
|
+
* draws, so it stays visible. */
|
|
3858
|
+
position: absolute;
|
|
3859
|
+
right: 24px;
|
|
3860
|
+
top: 50%;
|
|
3861
|
+
transform: translateY(-50%);
|
|
3862
|
+
background: transparent;
|
|
3863
|
+
border: 0;
|
|
3864
|
+
color: var(--ea-text-muted, #888);
|
|
3865
|
+
cursor: pointer;
|
|
3866
|
+
padding: 0.15em 0.35em;
|
|
3867
|
+
font-size: 1.1em;
|
|
3868
|
+
z-index: 1;
|
|
3869
|
+
}
|
|
3870
|
+
.twm-attr-editor__revert:hover {
|
|
3871
|
+
color: #d18138;
|
|
3872
|
+
}
|
|
3077
3873
|
|
|
3078
|
-
.
|
|
3874
|
+
/* ── from notebook.css ───────────────────────── */
|
|
3875
|
+
.twm-plot-no-data { padding: var(--space-2xl); text-align: center; color: var(--text-muted); font-size: var(--font-size-md); }
|
|
3876
|
+
/* ─── Autocomplete field — notebook variant ──────────────── */
|
|
3877
|
+
|
|
3878
|
+
.twm-autocomplete-field--notebook .twm-autocomplete-field__input-wrapper {
|
|
3879
|
+
border: 1px solid #569cd6;
|
|
3880
|
+
font-size: var(--font-size-sm);
|
|
3881
|
+
}
|
|
3882
|
+
.twm-autocomplete-field--notebook .twm-autocomplete-field__input-wrapper:focus-within {
|
|
3883
|
+
border-color: #79b8f8;
|
|
3884
|
+
box-shadow: 0 0 3px rgba(86, 156, 214, 0.3);
|
|
3885
|
+
}
|
|
3886
|
+
.twm-autocomplete-field--notebook .twm-autocomplete-field__input {
|
|
3887
|
+
font-family: 'Courier New', Courier, monospace;
|
|
3888
|
+
color: var(--text-body);
|
|
3889
|
+
font-size: var(--font-size-sm);
|
|
3890
|
+
}
|
|
3891
|
+
.twm-autocomplete-field--notebook .twm-autocomplete-field__helper {
|
|
3892
|
+
display: none;
|
|
3893
|
+
}
|
|
3894
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
3895
|
+
Tree Item Badge — small status indicator on file tree items
|
|
3896
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
3897
|
+
|
|
3898
|
+
.twm-tree-item__badge {
|
|
3899
|
+
display: inline-flex;
|
|
3900
|
+
align-items: center;
|
|
3901
|
+
justify-content: center;
|
|
3902
|
+
min-width: 16px;
|
|
3903
|
+
height: 16px;
|
|
3904
|
+
padding: 0 4px;
|
|
3905
|
+
border-radius: var(--radius-lg);
|
|
3906
|
+
font-size: 9px;
|
|
3907
|
+
font-weight: 600;
|
|
3908
|
+
flex-shrink: 0;
|
|
3909
|
+
margin-left: auto;
|
|
3910
|
+
line-height: 1;
|
|
3911
|
+
}
|
|
3912
|
+
/* Status dot variant (smaller, no text) */
|
|
3913
|
+
.twm-tree-item__status-dot {
|
|
3914
|
+
width: 7px;
|
|
3915
|
+
height: 7px;
|
|
3916
|
+
border-radius: 50%;
|
|
3917
|
+
flex-shrink: 0;
|
|
3918
|
+
margin-left: auto;
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3921
|
+
/* ── from slide_out_panel.css ───────────────────────── */
|
|
3922
|
+
/* ─── SlideOutPanel ─────────────────────────────────────────────────────────
|
|
3923
|
+
Inline slide-out config panel that overlays its host container from
|
|
3924
|
+
the right edge. Host container must have position:relative; overflow:hidden.
|
|
3925
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
3926
|
+
|
|
3927
|
+
/* Transparent click-catcher behind the panel. Hidden by default
|
|
3928
|
+
* via `visibility: hidden` (not just display) so it's removed
|
|
3929
|
+
* from the accessibility tree + paint pass when the panel is
|
|
3930
|
+
* closed; the `--visible` modifier flips it back on. */
|
|
3931
|
+
.twm-slide-out-panel__backdrop {
|
|
3932
|
+
position: absolute;
|
|
3933
|
+
inset: 0;
|
|
3934
|
+
z-index: 999;
|
|
3935
|
+
visibility: hidden;
|
|
3936
|
+
pointer-events: none;
|
|
3937
|
+
}
|
|
3938
|
+
.twm-slide-out-panel__backdrop--visible {
|
|
3939
|
+
visibility: visible;
|
|
3940
|
+
pointer-events: auto;
|
|
3941
|
+
}
|
|
3942
|
+
/* Panel container. When closed it sits at translateX(100%) off the
|
|
3943
|
+
* right edge of the host; `visibility: hidden` on the closed state
|
|
3944
|
+
* stops the slid-off panel from intercepting clicks or leaking
|
|
3945
|
+
* paint artifacts at the right edge of the workspace. */
|
|
3946
|
+
.twm-slide-out-panel {
|
|
3947
|
+
position: absolute;
|
|
3948
|
+
right: 0;
|
|
3949
|
+
top: 0;
|
|
3950
|
+
height: 100%;
|
|
3951
|
+
z-index: var(--z-modal);
|
|
3079
3952
|
display: flex;
|
|
3080
3953
|
flex-direction: column;
|
|
3081
|
-
|
|
3082
|
-
|
|
3954
|
+
|
|
3955
|
+
background: #212121;
|
|
3956
|
+
border-left: 1px solid #333;
|
|
3957
|
+
box-shadow: -4px 0 12px rgba(0, 0, 0, 0.35);
|
|
3958
|
+
|
|
3959
|
+
min-width: 400px;
|
|
3960
|
+
|
|
3961
|
+
transform: translateX(100%);
|
|
3962
|
+
transition: transform 200ms ease-out, visibility 0s linear 200ms;
|
|
3963
|
+
visibility: hidden;
|
|
3083
3964
|
}
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
* (`.twm-attr-editor__header-placeholder`) reserve the handle + remove
|
|
3089
|
-
* gutters; the inner `__headers` div is the column-grid that aligns
|
|
3090
|
-
* with each row's `.twm-attr-editor__row` grid. */
|
|
3091
|
-
.twm-attr-editor__header-row {
|
|
3092
|
-
background: transparent;
|
|
3093
|
-
border: 1px solid transparent; /* keep height parity with data rows */
|
|
3094
|
-
cursor: default;
|
|
3965
|
+
.twm-slide-out-panel--open {
|
|
3966
|
+
transform: translateX(0);
|
|
3967
|
+
visibility: visible;
|
|
3968
|
+
transition: transform 200ms ease-out, visibility 0s linear 0s;
|
|
3095
3969
|
}
|
|
3096
|
-
|
|
3097
|
-
|
|
3970
|
+
/* Header bar */
|
|
3971
|
+
.twm-slide-out-panel__header {
|
|
3972
|
+
display: flex;
|
|
3973
|
+
align-items: center;
|
|
3974
|
+
gap: var(--space-sm);
|
|
3975
|
+
padding: var(--space-md) 8px 8px 12px;
|
|
3976
|
+
border-bottom: 1px solid var(--border-strong);
|
|
3977
|
+
flex-shrink: 0;
|
|
3978
|
+
user-select: none;
|
|
3979
|
+
}
|
|
3980
|
+
.twm-slide-out-panel__title {
|
|
3981
|
+
flex: 1;
|
|
3982
|
+
font-size: var(--font-size-sm);
|
|
3983
|
+
font-weight: 600;
|
|
3984
|
+
color: var(--text-secondary);
|
|
3985
|
+
white-space: nowrap;
|
|
3986
|
+
overflow: hidden;
|
|
3987
|
+
text-overflow: ellipsis;
|
|
3988
|
+
}
|
|
3989
|
+
.twm-slide-out-panel__close {
|
|
3990
|
+
flex-shrink: 0;
|
|
3991
|
+
margin-left: auto;
|
|
3992
|
+
}
|
|
3993
|
+
/* Scrollable body */
|
|
3994
|
+
.twm-slide-out-panel__body {
|
|
3995
|
+
flex: 1;
|
|
3996
|
+
overflow-y: auto;
|
|
3997
|
+
padding: var(--space-md);
|
|
3998
|
+
}
|
|
3999
|
+
|
|
4000
|
+
/* ── from plot_config.css ───────────────────────── */
|
|
4001
|
+
.twm-btn-icon {
|
|
4002
|
+
background: transparent;
|
|
4003
|
+
border: none;
|
|
4004
|
+
color: #8a8f98;
|
|
4005
|
+
cursor: pointer;
|
|
4006
|
+
padding: var(--space-xs);
|
|
4007
|
+
border-radius: var(--radius-sm);
|
|
4008
|
+
display: flex;
|
|
4009
|
+
align-items: center;
|
|
4010
|
+
justify-content: center;
|
|
4011
|
+
transition: color var(--transition-normal), background-color var(--transition-normal);
|
|
4012
|
+
}
|
|
4013
|
+
.twm-btn-icon:hover {
|
|
4014
|
+
color: var(--text-bright);
|
|
4015
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
3098
4016
|
}
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
4017
|
+
|
|
4018
|
+
/* ── from tiling/host.css ───────────────────────── */
|
|
4019
|
+
@media (max-width: 900px) {
|
|
4020
|
+
.twm-top-nav__label { display: none; }
|
|
4021
|
+
.twm-top-nav__btn { padding: 0 10px; }
|
|
3102
4022
|
}
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
4023
|
+
|
|
4024
|
+
|
|
4025
|
+
/* ══ RESTORED MIRROR ═════════════════════════════════════════════════
|
|
4026
|
+
* `css/flexdesk.css` is generated from this file by `scripts/css_extract.py`,
|
|
4027
|
+
* which is not in the repository — so the two are kept in step BY HAND, and
|
|
4028
|
+
* this block is that mirror re-established after the working tree lost its
|
|
4029
|
+
* uncommitted changes. Every rule below was recovered from the built
|
|
4030
|
+
* stylesheet and is already live in `css/flexdesk.css`; they are grouped
|
|
4031
|
+
* here rather than threaded back into their original sections because the
|
|
4032
|
+
* generator, not this file, decides where they end up.
|
|
4033
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
4034
|
+
|
|
4035
|
+
/* Selected rows. `_updateRowSelection()` (src/ui/components/data_table.js:2226)
|
|
4036
|
+
has always toggled `.selected` on the row, and no stylesheet has ever
|
|
4037
|
+
defined it — so `selectable: true`, the component's headline affordance,
|
|
4038
|
+
has selected INVISIBLY in every consumer since the class was written.
|
|
4039
|
+
Painted on `td` rather than `tr` to match the hover rule above: the zebra
|
|
4040
|
+
stripe sits on `tr`, and a `tr` background would fight it at equal
|
|
4041
|
+
specificity instead of covering it.
|
|
4042
|
+
|
|
4043
|
+
AND IT HAS TO OUT-SPECIFY HOVER, not merely follow it. Source order only
|
|
4044
|
+
decides a TIE, and there is none here: the hover rule above is (0,3,2) —
|
|
4045
|
+
`:not()` contributes the specificity of its argument — against (0,2,3) for
|
|
4046
|
+
the plain form. Written that way, pointing at the row you had chosen
|
|
4047
|
+
un-highlights it, which reads as the selection being lost on mouse-over. The
|
|
4048
|
+
first selector below carries the same `:not()` and lands at (0,3,3); the
|
|
4049
|
+
second keeps the readonly tables, which hover never claimed, working. */
|
|
4050
|
+
.twm-preview-table:not(.twm-preview-table--readonly) tbody tr.selected td,
|
|
4051
|
+
.twm-preview-table tbody tr.selected td {
|
|
4052
|
+
background: var(--state-selected, rgba(0, 122, 204, 0.15));
|
|
3111
4053
|
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
*
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
4054
|
+
|
|
4055
|
+
padding: 0;
|
|
4056
|
+
/* A BOX, not "whatever the glyph turned out to be". The rule had padding
|
|
4057
|
+
* and no dimensions, so the button's size was the icon's size — and the
|
|
4058
|
+
* icon's size was nobody's decision at all (see below). In a 35px top bar
|
|
4059
|
+
* that produced a ~32px control that visibly outweighed everything beside
|
|
4060
|
+
* it. */
|
|
4061
|
+
width: 22px;
|
|
4062
|
+
height: 22px;
|
|
4063
|
+
/* THE GLYPH SIZE CAME ACROSS AS NOTHING.
|
|
4064
|
+
*
|
|
4065
|
+
* EcoAgent sets it — `main_new.css:4700`, `.panel-toggle-btn
|
|
4066
|
+
* .material-symbols-outlined { font-size: var(--font-size-xl) }` — and the
|
|
4067
|
+
* extraction that produced this stylesheet took the button rule and left that
|
|
4068
|
+
* one behind. Material Symbols then falls back to its own default of 24px, so
|
|
4069
|
+
* every consumer of the library has been drawing this control half again as
|
|
4070
|
+
* large as its author intended. Same family as the `--ea-*` variables that
|
|
4071
|
+
* arrived without their definitions, and as `.twm-collapsible-content.visible`:
|
|
4072
|
+
* an extraction that split a rule from the rule it depended on.
|
|
4073
|
+
*
|
|
4074
|
+
* `font-size` IS the glyph size for an icon font. `width`/`height` on the
|
|
4075
|
+
* button does not constrain it — it overflows instead. */
|
|
4076
|
+
.twm-panel-toggle-btn .material-symbols-outlined {
|
|
4077
|
+
font-size: var(--font-size-xl);
|
|
4078
|
+
line-height: 1;
|
|
3123
4079
|
}
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
4080
|
+
|
|
4081
|
+
/* `#open()` adds `visible` and `#close()` takes it off again
|
|
4082
|
+
(`ui/components/autocomplete_field.js`), and until now NEITHER stylesheet
|
|
4083
|
+
said what the class meant — while the dropdown above is `display: none`. So
|
|
4084
|
+
the suggestion list has never been shown to anybody, in any consumer, for as
|
|
4085
|
+
long as the component has existed: the provider ran, the items rendered, and
|
|
4086
|
+
the element stayed hidden. This is the same shape as
|
|
4087
|
+
`.twm-action-dropdown-menu.visible`, which is the pattern the component was
|
|
4088
|
+
written against. */
|
|
4089
|
+
.twm-autocomplete-field__dropdown.visible {
|
|
4090
|
+
display: block;
|
|
3133
4091
|
}
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
-
|
|
4092
|
+
|
|
4093
|
+
/* `#renderDropdown()` marks the keyboard cursor's row `active` and
|
|
4094
|
+
`#moveSelection()` moves it — with no rule to draw it. Arrowing down the list
|
|
4095
|
+
therefore moved something invisible, and Enter picked a row the user had no
|
|
4096
|
+
way to see. `:hover` above is the mouse's answer to the same question; this
|
|
4097
|
+
is the keyboard's, and it is deliberately the stronger of the two so that
|
|
4098
|
+
moving the pointer across the list does not hide where the cursor is. */
|
|
4099
|
+
.twm-autocomplete-field__item.active {
|
|
4100
|
+
background-color: var(--state-selected, rgba(0, 122, 204, 0.15));
|
|
4101
|
+
box-shadow: inset 2px 0 0 var(--accent-bright, #007acc);
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
/* A `<button role="menuitem">`, not a div — that is what makes the menu
|
|
4105
|
+
* operable by keyboard without re-implementing Enter, Space and focus. The
|
|
4106
|
+
* first five declarations are the button reset that buys it: without them the
|
|
4107
|
+
* UA paints its own background, border and centred text over everything below. */
|
|
3143
4108
|
appearance: none;
|
|
3144
|
-
background
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
4109
|
+
background: none;
|
|
4110
|
+
border: 0;
|
|
4111
|
+
width: 100%;
|
|
4112
|
+
text-align: left;
|
|
4113
|
+
color: inherit;
|
|
4114
|
+
.twm-context-menu-item:hover:not(.disabled),
|
|
4115
|
+
.twm-context-menu-item:focus-visible {
|
|
4116
|
+
outline: none;
|
|
4117
|
+
/* A DISABLED ITEM MUST LOOK DISABLED.
|
|
4118
|
+
*
|
|
4119
|
+
* `showContextMenu` has always added `.disabled` and bound no click listener to
|
|
4120
|
+
* it — the behaviour was right and there was no rule for the class anywhere, so
|
|
4121
|
+
* a disabled item rendered at full opacity, kept `cursor: pointer`, still
|
|
4122
|
+
* highlighted on hover, and did nothing when clicked. That is the exact shape of
|
|
4123
|
+
* the `.twm-collapsible-content.visible` bug: a class the JS sets, a stylesheet
|
|
4124
|
+
* that has never heard of it, and a feature that looks live and is not. */
|
|
4125
|
+
.twm-context-menu-item.disabled {
|
|
4126
|
+
opacity: 0.4;
|
|
4127
|
+
cursor: default;
|
|
4128
|
+
color: var(--text-muted);
|
|
3150
4129
|
}
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
4130
|
+
|
|
4131
|
+
.twm-context-menu-item.disabled.twm-delete-node { color: var(--text-muted); }
|
|
4132
|
+
|
|
4133
|
+
/* THE OPEN STATE. `ActionDropdown.open()` un-hides the menu and then adds
|
|
4134
|
+
`.visible` on the next frame to fade it in (action_dropdown.js:172-180) —
|
|
4135
|
+
and no rule for that class existed, so the menu unhid itself into
|
|
4136
|
+
`opacity: 0; pointer-events: none` and stayed there. Every ActionDropdown in
|
|
4137
|
+
the library, "Add Widget" included, opened invisible and un-clickable.
|
|
4138
|
+
The same defect `.twm-collapsible-content.visible` had, in the same shape:
|
|
4139
|
+
the JS toggles a class the stylesheet never grew. */
|
|
4140
|
+
.twm-action-dropdown-menu.visible {
|
|
4141
|
+
opacity: 1;
|
|
4142
|
+
pointer-events: auto;
|
|
3154
4143
|
}
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
4144
|
+
|
|
4145
|
+
/* The option's text column. `_renderOptions` wraps the label and the
|
|
4146
|
+
description in `<span class="option-text">` (action_dropdown.js:125-128) and
|
|
4147
|
+
both children are inline, so a described option rendered as
|
|
4148
|
+
"Add Widget Put a widget on this desktop" on one run-together line. The
|
|
4149
|
+
description is a SECOND LINE by construction; this is what makes it one.
|
|
4150
|
+
`min-width: 0` so the ellipsis on the two children has something to bite on
|
|
4151
|
+
inside the flex row. */
|
|
4152
|
+
.twm-action-dropdown-option .option-text {
|
|
4153
|
+
display: flex;
|
|
4154
|
+
flex-direction: column;
|
|
4155
|
+
min-width: 0;
|
|
3159
4156
|
}
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
4157
|
+
|
|
4158
|
+
/* C11. Aero snap — the preview shown while a drag is over an edge zone.
|
|
4159
|
+
* Absolutely positioned in the SAME coordinate space the window uses, so it is
|
|
4160
|
+
* the container when contained and the page otherwise. Below the window's own
|
|
4161
|
+
* z-index and above everything else in the container, so it reads as a target
|
|
4162
|
+
* behind the thing being dragged. */
|
|
4163
|
+
.twm-snap-preview {
|
|
4164
|
+
position: absolute;
|
|
4165
|
+
z-index: 5999;
|
|
4166
|
+
pointer-events: none;
|
|
4167
|
+
background: rgba(23, 119, 179, 0.18);
|
|
4168
|
+
border: 1px solid var(--accent-bright);
|
|
4169
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
|
4170
|
+
transition: left 0.09s ease, top 0.09s ease, width 0.09s ease, height 0.09s ease;
|
|
3166
4171
|
}
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
4172
|
+
|
|
4173
|
+
/* C15. A CONTROLLER'S preview is positioned in VIEWPORT pixels, because its
|
|
4174
|
+
* rectangle came from hit-testing somebody else's DOM (`getBoundingClientRect`)
|
|
4175
|
+
* rather than from `_bounds()`. `position: fixed` is what makes those numbers
|
|
4176
|
+
* mean what they say; leaving it `absolute` under `document.body` would be
|
|
4177
|
+
* right only until the page scrolls. */
|
|
4178
|
+
.twm-snap-preview--viewport {
|
|
4179
|
+
position: fixed;
|
|
3170
4180
|
}
|
|
3171
|
-
|
|
4181
|
+
|
|
4182
|
+
/* A window whose position came from a snap rather than from a drag. Nothing
|
|
4183
|
+
* depends on it visually; it is there so a consumer can tell the two apart. */
|
|
4184
|
+
.twm-managed-window--snapped { }
|
|
4185
|
+
|
|
4186
|
+
/* R3. HALF TRANSPARENT THE MOMENT THE DRAG LEAVES THE TILE IT CAME FROM.
|
|
3172
4187
|
*
|
|
3173
|
-
*
|
|
3174
|
-
*
|
|
3175
|
-
*
|
|
3176
|
-
*
|
|
3177
|
-
*
|
|
3178
|
-
* revert button on the right.
|
|
3179
|
-
* --neutral: no parent archetype defines this attribute. Default
|
|
3180
|
-
* styling.
|
|
4188
|
+
* The one signal that separates "I am moving this window around on its own
|
|
4189
|
+
* pane" from "letting go now will put this window into the tree somewhere".
|
|
4190
|
+
* Inside its origin pane the window is fully opaque and nothing is armed; the
|
|
4191
|
+
* moment the pointer crosses out of it the window goes translucent and the drop
|
|
4192
|
+
* preview starts drawing what a release would produce.
|
|
3181
4193
|
*
|
|
3182
|
-
*
|
|
3183
|
-
*
|
|
3184
|
-
*
|
|
3185
|
-
|
|
3186
|
-
.
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
transition: background 120ms, border-color 120ms;
|
|
4194
|
+
* No transition of its own: `.twm-managed-window` already transitions `opacity`
|
|
4195
|
+
* over 0.2s for the minimize animation, so the fade comes for free — and adding
|
|
4196
|
+
* a second, faster one here would make the same property animate at two speeds
|
|
4197
|
+
* depending on which class won. Translucent, not ghostly: the window is still
|
|
4198
|
+
* the thing being aimed, and at 0.5 the pane underneath reads through it while
|
|
4199
|
+
* its title bar stays legible. */
|
|
4200
|
+
.twm-managed-window--detached {
|
|
4201
|
+
opacity: 0.5;
|
|
3191
4202
|
}
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
*
|
|
3195
|
-
*
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
4203
|
+
|
|
4204
|
+
/* A title-bar button the WM has suppressed for the window's current state — the
|
|
4205
|
+
* minimize button on a maximised window it promoted out of a tile. `!important`
|
|
4206
|
+
* because `button.twm-managed-window__btn` sets `display: flex` and an author
|
|
4207
|
+
* declaration of `display` defeats the `hidden` attribute outright; a class
|
|
4208
|
+
* that loses the specificity tie is a control that stays on screen. */
|
|
4209
|
+
.twm-managed-window__btn--suppressed { display: none !important; }
|
|
4210
|
+
|
|
4211
|
+
|
|
4212
|
+
/* C6. An extra titlebar button (the table cogwheel), sized like the built-ins.
|
|
4213
|
+
* R7. And the maximize button, when a consumer has given it a glyph instead of
|
|
4214
|
+
* the built-in square — the WM does, because for a window it lifted out of a
|
|
4215
|
+
* tile that button means "back to tile". A Material Symbol renders at its
|
|
4216
|
+
* `font-size` and Google's own sheet says 24px, which is three times the height
|
|
4217
|
+
* of a 20px title-bar button: the rule has to be a DESCENDANT selector like
|
|
4218
|
+
* this one to out-specify it, and a class on the glyph element itself would
|
|
4219
|
+
* quietly lose. */
|
|
4220
|
+
.twm-managed-window__btn--custom .material-symbols-outlined,
|
|
4221
|
+
.twm-managed-window__btn--maximize .material-symbols-outlined {
|
|
4222
|
+
/* The eight handles' geometry. Without these the elements exist, are
|
|
4223
|
+
* `position: absolute`, and have NO width, height or offset — so every window
|
|
4224
|
+
* in every consumer is unresizable and nothing says why. Edges are 6px, corners
|
|
4225
|
+
* 12px and sit above the edges, because a corner that loses to an edge makes
|
|
4226
|
+
* diagonal resize a matter of luck. */
|
|
4227
|
+
.twm-managed-window__resize--n,
|
|
4228
|
+
.twm-managed-window__resize--s { left: 0; right: 0; height: 6px; cursor: ns-resize; }
|
|
4229
|
+
|
|
4230
|
+
.twm-managed-window__resize--n { top: -3px; }
|
|
4231
|
+
|
|
4232
|
+
.twm-managed-window__resize--s { bottom: -3px; }
|
|
4233
|
+
|
|
4234
|
+
.twm-managed-window__resize--e,
|
|
4235
|
+
.twm-managed-window__resize--w { top: 0; bottom: 0; width: 6px; cursor: ew-resize; }
|
|
4236
|
+
|
|
4237
|
+
.twm-managed-window__resize--e { right: -3px; }
|
|
4238
|
+
|
|
4239
|
+
.twm-managed-window__resize--w { left: -3px; }
|
|
4240
|
+
|
|
4241
|
+
.twm-managed-window__resize--ne,
|
|
4242
|
+
.twm-managed-window__resize--nw,
|
|
4243
|
+
.twm-managed-window__resize--se,
|
|
4244
|
+
.twm-managed-window__resize--sw { width: 12px; height: 12px; z-index: calc(var(--z-sticky) + 1); }
|
|
4245
|
+
|
|
4246
|
+
.twm-managed-window__resize--ne { top: -3px; right: -3px; cursor: nesw-resize; }
|
|
4247
|
+
|
|
4248
|
+
.twm-managed-window__resize--nw { top: -3px; left: -3px; cursor: nwse-resize; }
|
|
4249
|
+
|
|
4250
|
+
.twm-managed-window__resize--se { bottom: -3px; right: -3px; cursor: nwse-resize; }
|
|
4251
|
+
|
|
4252
|
+
.twm-managed-window__resize--sw { bottom: -3px; left: -3px; cursor: nesw-resize; }
|
|
4253
|
+
|
|
4254
|
+
/* A maximised or snapped window is not resized by dragging its edges. */
|
|
4255
|
+
.twm-managed-window--maximized .twm-managed-window__resize { display: none; }
|
|
4256
|
+
|
|
4257
|
+
|
|
4258
|
+
/* C26. A LEADING GLYPH ON AN ACTION BUTTON — `openModal({actions:[{icon}]})`.
|
|
4259
|
+
*
|
|
4260
|
+
* Material Symbols is a FONT. It renders at `font-size`, which defaults to
|
|
4261
|
+
* 24px, and the button's `min-height` does not constrain it — so without this
|
|
4262
|
+
* the glyph overflows a 22px action button and the row reads as ragged. The
|
|
4263
|
+
* declaration is written TWICE, once as a single class and once compounded
|
|
4264
|
+
* with `.material-symbols-outlined`, because a consumer that loads Google's
|
|
4265
|
+
* hosted stylesheet loads it AFTER this one: `.material-symbols-outlined
|
|
4266
|
+
* {font-size:24px}` is a single class too, so it ties on specificity and wins
|
|
4267
|
+
* on source order. The compound selector is what actually holds. */
|
|
4268
|
+
.twm-btn__glyph,
|
|
4269
|
+
.twm-btn__glyph.material-symbols-outlined {
|
|
4270
|
+
font-size: var(--font-size-xl, 16px);
|
|
4271
|
+
line-height: 1;
|
|
4272
|
+
flex: 0 0 auto;
|
|
3199
4273
|
}
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
4274
|
+
|
|
4275
|
+
.twm-btn__label { line-height: 1; }
|
|
4276
|
+
|
|
4277
|
+
/* R1. THE CONTAINING BLOCK A DRAGGED WINDOW ESCAPES INTO. A window promoted
|
|
4278
|
+
* in place is contained to its leaf wrap (C21), which clips it; for the
|
|
4279
|
+
* length of a drag the WM re-parents it here instead, so it can be carried
|
|
4280
|
+
* across tile boundaries. Its `left`/`top` then resolve against this box,
|
|
4281
|
+
* and without a `position` they would resolve against whatever positioned
|
|
4282
|
+
* ancestor the embedder happens to have — usually `.twm-shell__main`, which
|
|
4283
|
+
* is the same rectangle, and occasionally the page, which is not.
|
|
4284
|
+
*
|
|
4285
|
+
* `relative` and nothing else, for the reason `.twm-leaf` gives at length:
|
|
4286
|
+
* `transform`, `filter` and `contain` would also capture the
|
|
4287
|
+
* `position: fixed` dropdowns that deliberately escape their tile. */
|
|
4288
|
+
position: relative;
|
|
4289
|
+
/* C21. THE CONTAINING BLOCK for a window promoted in place. `overflow:
|
|
4290
|
+
* hidden` below is what actually confines it; this is what makes its
|
|
4291
|
+
* coordinates mean the pane rather than the page.
|
|
4292
|
+
*
|
|
4293
|
+
* `position: relative` and NOTHING ELSE — `transform`, `filter`, `contain`
|
|
4294
|
+
* and `will-change` also create a containing block AND capture
|
|
4295
|
+
* `position: fixed` descendants, which would clip the dropdowns DataTable
|
|
4296
|
+
* opens on purpose. */
|
|
4297
|
+
position: relative;
|
|
4298
|
+
/* NO `text-transform` HERE, and the absence is the decision. This element used
|
|
4299
|
+
* to be lower-cased, which reads as a house style until you notice whose text
|
|
4300
|
+
* it is: the embedder's. `tile_renderer` puts a content factory's `title` in
|
|
4301
|
+
* here, and in a data application that is a record's display name — typed by a
|
|
4302
|
+
* user who capitalised "Customer Orders" on purpose. A framework may style its
|
|
4303
|
+
* own chrome however it likes; it may not rewrite its embedder's data on the
|
|
4304
|
+
* way to the screen. `.twm-leaf__tab-label` shows the same strings and lost the
|
|
4305
|
+
* same rule for the same reason. */
|
|
4306
|
+
/* C19. The tile's own glyph, left of the title, so a pane and the floating
|
|
4307
|
+
* window it promotes into look like the same thing — `.twm-managed-window__icon`
|
|
4308
|
+
* has always drawn one. Sized to the BUTTONS at the other end of the strip
|
|
4309
|
+
* rather than to the title, because those are the other icons in this bar.
|
|
4310
|
+
*
|
|
4311
|
+
* AND THE GLYPH HAS TO BE TOLD, exactly as on `.twm-leaf__btn` below: Material
|
|
4312
|
+
* Symbols is a font and renders at `font-size`, which defaults to 24px. Leaving
|
|
4313
|
+
* this off does not give a slightly large icon — it gives one half again as
|
|
4314
|
+
* tall as the 22px bar it sits in, which pushes the chrome open. */
|
|
4315
|
+
/* THE COMPOUND SELECTOR IS THE POINT.
|
|
4316
|
+
*
|
|
4317
|
+
* The element is `<span class="twm-leaf__icon material-symbols-outlined">`, and
|
|
4318
|
+
* Google's own stylesheet — which every consumer loads, AFTER this one — sets
|
|
4319
|
+
* `.material-symbols-outlined { font-size: 24px }`. Two single-class selectors
|
|
4320
|
+
* tie on specificity, so the LAST one wins, and the last one is theirs. A rule
|
|
4321
|
+
* written `.twm-leaf__icon { font-size: 14px }` therefore exists, is valid,
|
|
4322
|
+
* looks correct in review, and does nothing.
|
|
4323
|
+
*
|
|
4324
|
+
* `.twm-leaf__icon.material-symbols-outlined` is two classes and wins outright.
|
|
4325
|
+
* The same tie is why `.twm-panel-toggle-btn .material-symbols-outlined` works
|
|
4326
|
+
* — a descendant selector is also two — and it is the fourth outing of the same
|
|
4327
|
+
* defect family in this codebase: a rule for a class the other side already
|
|
4328
|
+
* claims. */
|
|
4329
|
+
.twm-leaf__icon,
|
|
4330
|
+
.twm-leaf__icon.material-symbols-outlined {
|
|
4331
|
+
flex: 0 0 auto;
|
|
4332
|
+
font-size: 14px;
|
|
4333
|
+
line-height: 1;
|
|
4334
|
+
color: var(--text-muted);
|
|
4335
|
+
font-variation-settings: 'opsz' 20, 'wght' 400;
|
|
3203
4336
|
}
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
4337
|
+
|
|
4338
|
+
/* Hidden, not empty, for a kind that has no glyph — the framework's own
|
|
4339
|
+
* `panel:*` tiles are in nobody's taxonomy. The chrome is a flex row with a
|
|
4340
|
+
* gap, and an empty span still takes its gap: the title would sit a glyph's
|
|
4341
|
+
* width off the left margin with nothing in front of it. */
|
|
4342
|
+
.twm-leaf__icon[hidden] { display: none; }
|
|
4343
|
+
|
|
4344
|
+
.twm-leaf__actions { display: inline-flex; gap: 2px; align-items: center; }
|
|
4345
|
+
|
|
4346
|
+
/* C18. The embedder's own buttons, to the LEFT of the structural ones and
|
|
4347
|
+
* separated from them — these act on the CONTENT, those act on the TILE. */
|
|
4348
|
+
.twm-leaf__actions--content {
|
|
4349
|
+
margin-left: auto;
|
|
4350
|
+
padding-right: var(--space-sm);
|
|
4351
|
+
margin-right: var(--space-sm);
|
|
4352
|
+
border-right: 1px solid var(--border-subtle);
|
|
3211
4353
|
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
4354
|
+
|
|
4355
|
+
.twm-leaf__actions--content[hidden] { display: none; }
|
|
4356
|
+
|
|
4357
|
+
/* THE GLYPH HAS TO BE TOLD. Material Symbols ships at `font-size: 24px`, and
|
|
4358
|
+
* the button is 18px square — so every icon overflowed its own button, the
|
|
4359
|
+
* strip read as oversized next to a 22px chrome bar, and the buttons visually
|
|
4360
|
+
* collided. `font-size` IS the glyph size for an icon font; `width`/`height` on
|
|
4361
|
+
* the button does not constrain it. */
|
|
4362
|
+
.twm-leaf__btn .material-symbols-outlined {
|
|
4363
|
+
font-size: 14px;
|
|
4364
|
+
line-height: 1;
|
|
4365
|
+
/* The variable-font axes the family exposes. At 14px the default optical
|
|
4366
|
+
* size (24) draws strokes too heavy to read as a small control. */
|
|
4367
|
+
font-variation-settings: 'opsz' 20, 'wght' 400;
|
|
3215
4368
|
}
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
4369
|
+
|
|
4370
|
+
.twm-leaf__btn:hover:not(:disabled) { background: var(--state-hover); color: var(--text-primary); }
|
|
4371
|
+
|
|
4372
|
+
/* C20. A verb that applies to this kind of pane but not right now — greyed and
|
|
4373
|
+
* explained by its tooltip, rather than removed. `:disabled` alone would still
|
|
4374
|
+
* take the hover highlight above, which reads as clickable. */
|
|
4375
|
+
.twm-leaf__btn--disabled,
|
|
4376
|
+
.twm-leaf__btn:disabled {
|
|
4377
|
+
opacity: 0.35;
|
|
4378
|
+
cursor: default;
|
|
3220
4379
|
}
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
4380
|
+
|
|
4381
|
+
/* R8. THE SAME STRIP, IN A WINDOW. A pane floated into a managed window takes
|
|
4382
|
+
* its tabs with it — *"to window includes the tab-strip"* — and the strip in
|
|
4383
|
+
* the window is the strip from the pane: same component, same vocabulary, same
|
|
4384
|
+
* height. So it is the same rule, with a second selector, rather than a second
|
|
4385
|
+
* block that would drift from this one the first time either was touched.
|
|
4386
|
+
* `.twm-window-tabbar` is always the `--top` variant (a window's chrome is its
|
|
4387
|
+
* title bar, and the tabs go under it), so it appears in that block too. */
|
|
4388
|
+
.twm-leaf__tabbar,
|
|
4389
|
+
.twm-window-tabbar {
|
|
4390
|
+
/* RESTORED. This block's DECLARATIONS were missing — the selector list
|
|
4391
|
+
* opened a brace and the next rule's selectors were swallowed as its body,
|
|
4392
|
+
* which also took `--hidden` down with it: a CSS parser recovers at the
|
|
4393
|
+
* matching `}`, so BOTH rules were dead. `css/flexdesk.css` (the sheet
|
|
4394
|
+
* `build.mjs` actually ships, mirrored by hand because `css_extract.py` is
|
|
4395
|
+
* not in this repository) still carried them, which is why no consumer
|
|
4396
|
+
* loading `dist/` ever saw it — and why nothing caught it. What a consumer
|
|
4397
|
+
* loading `base.css` got was a tab strip with no height, no background and
|
|
4398
|
+
* `flex-shrink: 1`, and a `--hidden` modifier that hid nothing. */
|
|
4399
|
+
flex: 0 0 auto;
|
|
4400
|
+
display: flex;
|
|
4401
|
+
align-items: stretch;
|
|
4402
|
+
height: 22px;
|
|
4403
|
+
background: var(--surface-1);
|
|
4404
|
+
border-top: 1px solid var(--border-default);
|
|
4405
|
+
padding: 0 4px;
|
|
4406
|
+
overflow: hidden;
|
|
4407
|
+
user-select: none;
|
|
4408
|
+
font-size: var(--font-size-xs);
|
|
3225
4409
|
}
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
4410
|
+
.twm-leaf__tabbar--hidden,
|
|
4411
|
+
.twm-window-tabbar--hidden { display: none; }
|
|
4412
|
+
|
|
4413
|
+
/* Lower-casing removed here too — a tab label is the same embedder-supplied
|
|
4414
|
+
* title as `.twm-leaf__title`, just in the strip at the bottom of the leaf. */
|
|
4415
|
+
|
|
4416
|
+
/* ══ C22. THE TOP TAB LAYOUT ═════════════════════════════════════════
|
|
3229
4417
|
*
|
|
3230
|
-
*
|
|
3231
|
-
*
|
|
3232
|
-
*
|
|
3233
|
-
*
|
|
3234
|
-
*
|
|
3235
|
-
*
|
|
3236
|
-
*
|
|
3237
|
-
*
|
|
3238
|
-
|
|
3239
|
-
.
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
4418
|
+
* `tabLayout: 'top'` puts the strip BETWEEN the chrome and the body and draws
|
|
4419
|
+
* it with `NotebookTabBar` — the editor's tab bar — instead of the spreadsheet
|
|
4420
|
+
* strip above. The bar element is the same `.twm-leaf__tabbar`, so everything
|
|
4421
|
+
* it already carries (the fixed height, `--hidden`, `flex: 0 0 auto`) still
|
|
4422
|
+
* applies; this modifier only turns the strip the right way up.
|
|
4423
|
+
*
|
|
4424
|
+
* The border moves with it. A strip at the bottom of a tile is separated from
|
|
4425
|
+
* the body above it by a top border; a strip at the top is separated from the
|
|
4426
|
+
* body BELOW it by a bottom one, and keeping the top border as well would draw
|
|
4427
|
+
* a second line a pixel under the chrome's own. */
|
|
4428
|
+
.twm-leaf__tabbar--top,
|
|
4429
|
+
.twm-window-tabbar {
|
|
4430
|
+
border-top: none;
|
|
4431
|
+
border-bottom: 1px solid var(--border-default);
|
|
4432
|
+
/* The editor tabs are a touch taller than a 22px spreadsheet tab: they
|
|
4433
|
+
* carry a glyph, a label and a close button on one line, and 22px leaves
|
|
4434
|
+
* the close button touching the label's descenders. */
|
|
4435
|
+
height: 26px;
|
|
4436
|
+
padding: 0;
|
|
4437
|
+
/* `overflow: hidden` on the bottom strip clips the horizontal scroll the
|
|
4438
|
+
* tab list wants. The list scrolls itself (below); the BAR must not. */
|
|
4439
|
+
overflow: visible;
|
|
3245
4440
|
}
|
|
3246
|
-
|
|
3247
|
-
.
|
|
3248
|
-
|
|
3249
|
-
|
|
4441
|
+
|
|
4442
|
+
/* `NotebookTabBar.mount()` assigns `className = 'tabs notebook-tabs'` to
|
|
4443
|
+
* whatever container it is handed (`notebook_tab_bar.js:61`) — it does not add
|
|
4444
|
+
* to it — which is why the renderer mounts it into a child of the bar rather
|
|
4445
|
+
* than into the bar. The child is that container, and it fills the bar. */
|
|
4446
|
+
.twm-leaf__tabbar--top > .notebook-tabs,
|
|
4447
|
+
.twm-window-tabbar > .notebook-tabs { flex: 1 1 auto; min-width: 0; }
|
|
4448
|
+
|
|
4449
|
+
/* A window's body is what the content mounts into, below the strip. It is
|
|
4450
|
+
* the box `.twm-managed-window__content` used to be for a window with no
|
|
4451
|
+
* tabs, so it carries the same three declarations — without them a grid
|
|
4452
|
+
* inside a floated pane sizes to its rows rather than to the window and the
|
|
4453
|
+
* strip is pushed off the bottom edge. */
|
|
4454
|
+
.twm-window-body { flex: 1 1 auto; min-height: 0; min-width: 0; }
|
|
4455
|
+
|
|
4456
|
+
|
|
4457
|
+
/* ══ NotebookTabBar ══════════════════════════════════════════════════
|
|
4458
|
+
*
|
|
4459
|
+
* A CLASS ONE SIDE KNOWS AND THE OTHER HAD NEVER HEARD OF — the fifth in this
|
|
4460
|
+
* codebase and the first one found before it shipped. `NotebookTabBar` has
|
|
4461
|
+
* rendered `.tabs` / `.tab` / `.tab-icon` / `.twm-tab-title` / `.tab-dirty` /
|
|
4462
|
+
* `.twm-tab-close` / `.nb-context-menu` since it was extracted, and NOT ONE of
|
|
4463
|
+
* those selectors existed in any FlexDesk stylesheet: the component imports
|
|
4464
|
+
* cleanly, mounts without error, is fully interactive, and draws as a row of
|
|
4465
|
+
* unstyled inline text with a `×` after each label. Nothing throws and no test
|
|
4466
|
+
* can see it, which is the signature of the whole family.
|
|
4467
|
+
*
|
|
4468
|
+
* The visual language is the tile chrome's, not an editor's: `--surface-1` bar,
|
|
4469
|
+
* `--surface-0` active tab so it reads as continuous with the body under it,
|
|
4470
|
+
* square corners, `--font-size-xs`. A tab bar drawn in one house style with the
|
|
4471
|
+
* chrome an inch above it in another is how a pane stops looking like one
|
|
4472
|
+
* object. */
|
|
4473
|
+
.tabs.notebook-tabs {
|
|
4474
|
+
display: flex;
|
|
4475
|
+
align-items: stretch;
|
|
4476
|
+
height: 100%;
|
|
4477
|
+
min-width: 0;
|
|
4478
|
+
background: var(--surface-1);
|
|
4479
|
+
user-select: none;
|
|
4480
|
+
font-size: var(--font-size-xs);
|
|
4481
|
+
/* SCROLL, DO NOT WRAP OR SHRINK. The tabs keep their width and the strip
|
|
4482
|
+
* scrolls under them — a tab that shrinks to nothing as you open the tenth
|
|
4483
|
+
* one is a tab you cannot read or aim at. The scrollbar itself is hidden:
|
|
4484
|
+
* it is a 26px strip, and a horizontal bar in it would eat a third of the
|
|
4485
|
+
* label. Wheel and drag still scroll it, and DragReorder auto-scrolls the
|
|
4486
|
+
* edges while a tab is being dragged (`drag_reorder.js:_autoScrollEdge`). */
|
|
4487
|
+
overflow-x: auto;
|
|
4488
|
+
overflow-y: hidden;
|
|
4489
|
+
scrollbar-width: none;
|
|
3250
4490
|
}
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
4491
|
+
|
|
4492
|
+
.tabs.notebook-tabs::-webkit-scrollbar { display: none; }
|
|
4493
|
+
|
|
4494
|
+
.notebook-tabs > .tab {
|
|
4495
|
+
position: relative;
|
|
4496
|
+
flex: 0 0 auto;
|
|
4497
|
+
display: inline-flex;
|
|
4498
|
+
align-items: center;
|
|
4499
|
+
gap: var(--space-xs);
|
|
4500
|
+
max-width: 200px;
|
|
4501
|
+
padding: 0 var(--space-sm);
|
|
4502
|
+
background: var(--surface-1);
|
|
4503
|
+
color: var(--text-muted);
|
|
4504
|
+
border: none;
|
|
4505
|
+
border-right: 1px solid var(--border-subtle);
|
|
4506
|
+
cursor: pointer;
|
|
4507
|
+
line-height: 1;
|
|
4508
|
+
white-space: nowrap;
|
|
3254
4509
|
}
|
|
3255
|
-
|
|
3256
|
-
.
|
|
3257
|
-
|
|
3258
|
-
|
|
4510
|
+
|
|
4511
|
+
.notebook-tabs > .tab:hover { background: var(--surface-2); color: var(--text-primary); }
|
|
4512
|
+
|
|
4513
|
+
/* The active tab is the body's colour and loses the line under it, so the tab
|
|
4514
|
+
* and the pane it shows read as one surface. The accent sits on TOP — the edge
|
|
4515
|
+
* furthest from the body — for the same reason a browser puts it there: it
|
|
4516
|
+
* marks the tab, not the boundary. */
|
|
4517
|
+
.notebook-tabs > .tab.active {
|
|
4518
|
+
background: var(--surface-0);
|
|
4519
|
+
color: var(--text-bright);
|
|
3259
4520
|
}
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
4521
|
+
|
|
4522
|
+
.notebook-tabs > .tab.active::before {
|
|
4523
|
+
content: '';
|
|
3263
4524
|
position: absolute;
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
transform: translateY(-50%);
|
|
3267
|
-
color: #d18138; /* solarized orange */
|
|
3268
|
-
font-size: 0.85em;
|
|
4525
|
+
inset: 0 0 auto 0;
|
|
4526
|
+
height: 2px;
|
|
3269
4527
|
pointer-events: none;
|
|
3270
|
-
|
|
3271
|
-
.twm-attr-editor__revert {
|
|
3272
|
-
/* Floats over the right edge of the row, outside the column grid.
|
|
3273
|
-
* Sits ABOVE the trailing remove-button gutter that sortable_list
|
|
3274
|
-
* draws, so it stays visible. */
|
|
3275
|
-
position: absolute;
|
|
3276
|
-
right: 24px;
|
|
3277
|
-
top: 50%;
|
|
3278
|
-
transform: translateY(-50%);
|
|
3279
|
-
background: transparent;
|
|
3280
|
-
border: 0;
|
|
3281
|
-
color: var(--ea-text-muted, #888);
|
|
3282
|
-
cursor: pointer;
|
|
3283
|
-
padding: 0.15em 0.35em;
|
|
3284
|
-
font-size: 1.1em;
|
|
3285
|
-
z-index: 1;
|
|
3286
|
-
}
|
|
3287
|
-
.twm-attr-editor__revert:hover {
|
|
3288
|
-
color: #d18138;
|
|
4528
|
+
background: var(--accent-bright, #1683c4);
|
|
3289
4529
|
}
|
|
3290
4530
|
|
|
3291
|
-
/*
|
|
3292
|
-
|
|
3293
|
-
|
|
4531
|
+
/* A PREVIEW tab — opened by a single click and replaced by the next one.
|
|
4532
|
+
* Italic is the convention every editor uses for "this one is not staying". */
|
|
4533
|
+
.notebook-tabs > .tab.tab--preview { font-style: italic; }
|
|
3294
4534
|
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
4535
|
+
/* THE GLYPH HAS TO BE TOLD, and it has to be told in TWO selectors.
|
|
4536
|
+
*
|
|
4537
|
+
* `<span class="material-symbols-outlined tab-icon">` carries two classes, and
|
|
4538
|
+
* Google's stylesheet — loaded by every consumer, AFTER this one — sets
|
|
4539
|
+
* `.material-symbols-outlined { font-size: 24px }`. A single-class rule here
|
|
4540
|
+
* ties on specificity and loses to whichever came last, which is theirs. The
|
|
4541
|
+
* compound selector is two classes and wins outright. Fourth outing of this
|
|
4542
|
+
* exact defect; it is why `.twm-leaf__icon` is written the same way. */
|
|
4543
|
+
.notebook-tabs .tab-icon,
|
|
4544
|
+
.notebook-tabs .tab-icon.material-symbols-outlined {
|
|
4545
|
+
flex: 0 0 auto;
|
|
4546
|
+
font-size: 14px;
|
|
4547
|
+
line-height: 1;
|
|
4548
|
+
font-variation-settings: 'opsz' 20, 'wght' 400;
|
|
3302
4549
|
}
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
4550
|
+
|
|
4551
|
+
.notebook-tabs .tab-icon[hidden] { display: none; }
|
|
4552
|
+
|
|
4553
|
+
/* The label. NOT lower-cased and not capitalised: it is the embedder's own
|
|
4554
|
+
* string — a record's display name, typed by a user who capitalised it on
|
|
4555
|
+
* purpose — for the same reason `.twm-leaf__title` lost that rule.
|
|
4556
|
+
*
|
|
4557
|
+
* AND IT NEEDS ITS OWN `line-height`, WHICH IS THE FIX FOR CLIPPED DESCENDERS.
|
|
4558
|
+
* `.tab` sets `line-height: 1` so the row's boxes stack predictably in a 26px
|
|
4559
|
+
* strip; this span sets `overflow: hidden` for the ellipsis, which makes it a
|
|
4560
|
+
* scroll container whose content box is exactly `1 × font-size` tall. Anything
|
|
4561
|
+
* drawn below the baseline — the tail of a `g`, a `y`, a `p` — falls outside
|
|
4562
|
+
* that box and is CLIPPED. It reads as a tab label with its bottom shaved off,
|
|
4563
|
+
* which is precisely how it was reported ("fix the cut-off text in the tabs -
|
|
4564
|
+
* cut off at bottom"), and it is invisible on any label that happens to have no
|
|
4565
|
+
* descender in it.
|
|
4566
|
+
*
|
|
4567
|
+
* 1.45 is the smallest value that clears the descender at every size this strip
|
|
4568
|
+
* is used at; the flex row still centres the span, so the tab's own height and
|
|
4569
|
+
* every other box in it are unchanged. */
|
|
4570
|
+
.notebook-tabs .twm-tab-title {
|
|
4571
|
+
overflow: hidden;
|
|
4572
|
+
text-overflow: ellipsis;
|
|
4573
|
+
letter-spacing: 0.02em;
|
|
4574
|
+
line-height: 1.45;
|
|
3307
4575
|
}
|
|
3308
|
-
|
|
3309
|
-
|
|
4576
|
+
|
|
4577
|
+
/* Unsaved work. A dot rather than a `*`, and it takes the close button's slot
|
|
4578
|
+
* rather than adding to the row — in most editors the two swap on hover, but a
|
|
4579
|
+
* dot that MOVES the close button as it appears makes the button a target that
|
|
4580
|
+
* shifts under the pointer. */
|
|
4581
|
+
.notebook-tabs .tab-dirty {
|
|
4582
|
+
flex: 0 0 auto;
|
|
4583
|
+
font-size: 9px;
|
|
4584
|
+
line-height: 1;
|
|
4585
|
+
color: var(--accent-bright, #1683c4);
|
|
3310
4586
|
}
|
|
3311
|
-
/* ═══════════════════════════════════════════════════════════════════════════
|
|
3312
|
-
Tree Item Badge — small status indicator on file tree items
|
|
3313
|
-
═══════════════════════════════════════════════════════════════════════════ */
|
|
3314
4587
|
|
|
3315
|
-
.twm-
|
|
4588
|
+
.notebook-tabs .twm-tab-close {
|
|
4589
|
+
flex: 0 0 auto;
|
|
3316
4590
|
display: inline-flex;
|
|
3317
4591
|
align-items: center;
|
|
3318
4592
|
justify-content: center;
|
|
3319
|
-
|
|
3320
|
-
height:
|
|
3321
|
-
padding: 0
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
4593
|
+
width: 14px;
|
|
4594
|
+
height: 14px;
|
|
4595
|
+
padding: 0;
|
|
4596
|
+
background: transparent;
|
|
4597
|
+
border: none;
|
|
4598
|
+
border-radius: var(--radius-sm);
|
|
4599
|
+
color: inherit;
|
|
4600
|
+
/* A `×` CHARACTER, not a Material Symbol — the component writes the glyph
|
|
4601
|
+
* itself (`notebook_tab_bar.js:168`), so this one is sized by `font-size`
|
|
4602
|
+
* on the button and there is no icon font to out-specify. */
|
|
4603
|
+
font-size: 13px;
|
|
3327
4604
|
line-height: 1;
|
|
4605
|
+
opacity: 0.55;
|
|
4606
|
+
cursor: pointer;
|
|
3328
4607
|
}
|
|
3329
|
-
|
|
3330
|
-
.twm-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
flex-shrink: 0;
|
|
3335
|
-
margin-left: auto;
|
|
4608
|
+
|
|
4609
|
+
.notebook-tabs .twm-tab-close:hover {
|
|
4610
|
+
opacity: 1;
|
|
4611
|
+
background: var(--state-hover);
|
|
4612
|
+
color: var(--text-bright);
|
|
3336
4613
|
}
|
|
3337
4614
|
|
|
3338
|
-
/* ──
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
4615
|
+
/* ── Reordering ──────────────────────────────────────────────────────
|
|
4616
|
+
* `DragReorder` puts `.dragging` on the tab under the pointer and inserts a
|
|
4617
|
+
* `.twm-tab-drop-indicator` element at the position it would land
|
|
4618
|
+
* (`drag_reorder.js:_start`, `_ensureIndicator`). Neither had a rule, so a
|
|
4619
|
+
* drag looked exactly like a click that did nothing until the tab arrived
|
|
4620
|
+
* somewhere else. */
|
|
4621
|
+
.notebook-tabs > .tab.dragging { opacity: 0.4; }
|
|
3343
4622
|
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
position: absolute;
|
|
3350
|
-
inset: 0;
|
|
3351
|
-
z-index: 999;
|
|
3352
|
-
visibility: hidden;
|
|
4623
|
+
.twm-tab-drop-indicator {
|
|
4624
|
+
flex: 0 0 auto;
|
|
4625
|
+
align-self: stretch;
|
|
4626
|
+
width: 2px;
|
|
4627
|
+
background: var(--accent-bright, #1683c4);
|
|
3353
4628
|
pointer-events: none;
|
|
3354
4629
|
}
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
4630
|
+
|
|
4631
|
+
/* ── C33. TWO MORE OF THE CLASS-ONE-SIDE-KNOWS FAMILY ────────────────
|
|
4632
|
+
*
|
|
4633
|
+
* Both of these have been set by JavaScript since the day they were
|
|
4634
|
+
* written and defined by NO stylesheet in this repository, so a
|
|
4635
|
+
* cross-pane drop has never drawn anything anywhere:
|
|
4636
|
+
*
|
|
4637
|
+
* `.notebook-tabs--drop-target` `notebook_tab_bar.js`,
|
|
4638
|
+
* `#onStripDragOver`
|
|
4639
|
+
* `.notebook-editor-container--drop-target` `../editor/editor_pane.js`,
|
|
4640
|
+
* its own `dragover`
|
|
4641
|
+
*
|
|
4642
|
+
* They are fixed here rather than left alone because C33 adds a THIRD
|
|
4643
|
+
* `--drop-target` affordance beside them (`.twm-leaf__tabbar--drop-target`,
|
|
4644
|
+
* in `css/overrides.css` with the other `.twm-leaf*` rules), and shipping a
|
|
4645
|
+
* live one next to two dead ones is how the family keeps growing. Same
|
|
4646
|
+
* declarations as the tile strip's, deliberately: three surfaces that mean
|
|
4647
|
+
* "let go here and this list takes it" should look like one thing.
|
|
4648
|
+
*
|
|
4649
|
+
* Neither direction of this defect is caught by tooling. `tools/css_check.mjs`
|
|
4650
|
+
* in the Tables consumer greps the JS side — a class the JS sets that no sheet
|
|
4651
|
+
* defines — but only for `tbl-`-prefixed names against that repository's own
|
|
4652
|
+
* stylesheet, so nothing here is in its scope at all. */
|
|
4653
|
+
.notebook-tabs--drop-target,
|
|
4654
|
+
.notebook-editor-container--drop-target {
|
|
4655
|
+
background: color-mix(in srgb, var(--accent-bright, #1683c4) 18%, transparent);
|
|
4656
|
+
box-shadow: inset 0 0 0 1px var(--accent-bright, #1683c4);
|
|
3358
4657
|
}
|
|
3359
|
-
/* Panel container. When closed it sits at translateX(100%) off the
|
|
3360
|
-
* right edge of the host; `visibility: hidden` on the closed state
|
|
3361
|
-
* stops the slid-off panel from intercepting clicks or leaking
|
|
3362
|
-
* paint artifacts at the right edge of the workspace. */
|
|
3363
|
-
.twm-slide-out-panel {
|
|
3364
|
-
position: absolute;
|
|
3365
|
-
right: 0;
|
|
3366
|
-
top: 0;
|
|
3367
|
-
height: 100%;
|
|
3368
|
-
z-index: var(--z-modal);
|
|
3369
|
-
display: flex;
|
|
3370
|
-
flex-direction: column;
|
|
3371
4658
|
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
4659
|
+
/* ── The tab bar's own context menu ──────────────────────────────────
|
|
4660
|
+
* Reached only by a consumer that lets the component open it — the tile
|
|
4661
|
+
* renderer suppresses it in favour of the WM's single tab menu, so these rules
|
|
4662
|
+
* are for `EditorPane` and anyone else mounting a `NotebookTabBar` directly.
|
|
4663
|
+
* Same geometry and the same tokens as `.twm-context-menu`, because two
|
|
4664
|
+
* context menus in one application that disagree about their own padding is
|
|
4665
|
+
* how a UI stops feeling assembled. */
|
|
4666
|
+
.nb-context-menu {
|
|
4667
|
+
position: fixed;
|
|
4668
|
+
z-index: 10050;
|
|
4669
|
+
min-width: 180px;
|
|
4670
|
+
padding: var(--space-xxs) 0;
|
|
4671
|
+
background: var(--surface-2);
|
|
4672
|
+
border: 1px solid var(--border-strong);
|
|
4673
|
+
border-radius: var(--radius-sm);
|
|
4674
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
|
|
4675
|
+
font-size: var(--font-size-xs);
|
|
4676
|
+
/* Opened hidden and revealed on the next frame (`#showContextMenu` adds
|
|
4677
|
+
* `.visible` in a rAF) so it can fade rather than snap — the same
|
|
4678
|
+
* open-then-reveal the collapsible primitive uses, and the same reason its
|
|
4679
|
+
* `.visible` rule had to exist before anything worked. */
|
|
4680
|
+
opacity: 0;
|
|
4681
|
+
transition: opacity 90ms ease-out;
|
|
4682
|
+
}
|
|
3375
4683
|
|
|
3376
|
-
|
|
4684
|
+
.nb-context-menu.visible { opacity: 1; }
|
|
3377
4685
|
|
|
3378
|
-
|
|
3379
|
-
transition: transform 200ms ease-out, visibility 0s linear 200ms;
|
|
3380
|
-
visibility: hidden;
|
|
3381
|
-
}
|
|
3382
|
-
.twm-slide-out-panel--open {
|
|
3383
|
-
transform: translateX(0);
|
|
3384
|
-
visibility: visible;
|
|
3385
|
-
transition: transform 200ms ease-out, visibility 0s linear 0s;
|
|
3386
|
-
}
|
|
3387
|
-
/* Header bar */
|
|
3388
|
-
.twm-slide-out-panel__header {
|
|
4686
|
+
.nb-context-menu__item {
|
|
3389
4687
|
display: flex;
|
|
3390
4688
|
align-items: center;
|
|
3391
4689
|
gap: var(--space-sm);
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
font-size: var(--font-size-sm);
|
|
3400
|
-
font-weight: 600;
|
|
3401
|
-
color: var(--text-secondary);
|
|
4690
|
+
width: 100%;
|
|
4691
|
+
padding: var(--space-xs) var(--space-md);
|
|
4692
|
+
background: transparent;
|
|
4693
|
+
border: none;
|
|
4694
|
+
color: var(--text-primary);
|
|
4695
|
+
text-align: left;
|
|
4696
|
+
cursor: pointer;
|
|
3402
4697
|
white-space: nowrap;
|
|
3403
|
-
overflow: hidden;
|
|
3404
|
-
text-overflow: ellipsis;
|
|
3405
4698
|
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
4699
|
+
|
|
4700
|
+
.nb-context-menu__item:hover { background: var(--state-hover); }
|
|
4701
|
+
|
|
4702
|
+
.nb-context-menu__item .material-symbols-outlined {
|
|
4703
|
+
font-size: 14px;
|
|
4704
|
+
line-height: 1;
|
|
4705
|
+
color: var(--text-muted);
|
|
4706
|
+
font-variation-settings: 'opsz' 20, 'wght' 400;
|
|
3409
4707
|
}
|
|
3410
|
-
|
|
3411
|
-
.
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
4708
|
+
|
|
4709
|
+
.nb-context-menu__item--danger { color: var(--color-danger, #c74e4e); }
|
|
4710
|
+
|
|
4711
|
+
.nb-context-menu__item--danger .material-symbols-outlined { color: inherit; }
|
|
4712
|
+
|
|
4713
|
+
/* DISABLED MEANS DISABLED. `.twm-context-menu-item.disabled` was the third
|
|
4714
|
+
* instance of this family — the JS had always set the class, no rule existed,
|
|
4715
|
+
* and a disabled item rendered at full opacity with a pointer cursor and did
|
|
4716
|
+
* nothing when clicked. `#showContextMenu` binds no listener on a disabled
|
|
4717
|
+
* item, so `pointer-events: none` is belt to that braces. */
|
|
4718
|
+
.nb-context-menu__item--disabled {
|
|
4719
|
+
opacity: 0.4;
|
|
4720
|
+
cursor: default;
|
|
4721
|
+
pointer-events: none;
|
|
3415
4722
|
}
|
|
3416
4723
|
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
color: #8a8f98;
|
|
3422
|
-
cursor: pointer;
|
|
3423
|
-
padding: var(--space-xs);
|
|
3424
|
-
border-radius: var(--radius-sm);
|
|
3425
|
-
display: flex;
|
|
3426
|
-
align-items: center;
|
|
3427
|
-
justify-content: center;
|
|
3428
|
-
transition: color var(--transition-normal), background-color var(--transition-normal);
|
|
4724
|
+
.nb-context-menu__shortcut {
|
|
4725
|
+
margin-left: auto;
|
|
4726
|
+
padding-left: var(--space-lg);
|
|
4727
|
+
color: var(--text-dim);
|
|
3429
4728
|
}
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
4729
|
+
|
|
4730
|
+
.nb-context-menu__separator {
|
|
4731
|
+
height: 1px;
|
|
4732
|
+
margin: var(--space-xxs) 0;
|
|
4733
|
+
background: var(--border-subtle);
|
|
3433
4734
|
}
|
|
3434
4735
|
|
|
3435
|
-
/*
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
4736
|
+
/* R1. The containing block an ESCAPED window resolves against — a window
|
|
4737
|
+
* dragged out of its pane is re-parented here for the length of the drag.
|
|
4738
|
+
* `relative` only, for the reason `.twm-leaf` documents: `transform`, `filter`,
|
|
4739
|
+
* `contain` and `will-change` each create a containing block AND trap
|
|
4740
|
+
* `position: fixed` descendants, which would clip every dropdown the grid opens
|
|
4741
|
+
* on purpose. */
|
|
4742
|
+
.twm-root { position: relative; }
|
|
4743
|
+
|
|
4744
|
+
/* ── C31. Content zoom ─────────────────────────────────────────────
|
|
4745
|
+
Applied by zoom.js as `--twm-zoom` plus `.twm-zoomed` on the shell
|
|
4746
|
+
ROOT. The class is only set away from 100%, so an unzoomed shell has
|
|
4747
|
+
no `zoom` declaration anywhere: `zoom: 1` is not a no-op, it still
|
|
4748
|
+
establishes a scaled coordinate space.
|
|
4749
|
+
|
|
4750
|
+
CONTENT SURFACES ONLY. A tile's body and a promoted window's content
|
|
4751
|
+
sit below every piece of geometry the window drag, resize and C15
|
|
4752
|
+
snap probe read. The root, a leaf wrap, tile chrome, tab bars and a
|
|
4753
|
+
window frame are deliberately absent: a contained window lives in a
|
|
4754
|
+
leaf wrap and is re-parented to the root while it is dragged, so
|
|
4755
|
+
zooming either would drift every drag by the zoom factor. See zoom.js. */
|
|
4756
|
+
.twm-zoomed .twm-leaf__body,
|
|
4757
|
+
.twm-zoomed .twm-window-content { zoom: var(--twm-zoom, 1); }
|
|
4758
|
+
|
|
4759
|
+
/* The control. Sized against the bar it sits in, not the zoom — it is
|
|
4760
|
+
chrome, and it is the one thing that must stay reachable at 200%. */
|
|
4761
|
+
.twm-zoom {
|
|
4762
|
+
display: inline-flex;
|
|
4763
|
+
align-items: center;
|
|
4764
|
+
gap: 3px;
|
|
4765
|
+
height: 100%;
|
|
4766
|
+
padding: 0 8px;
|
|
4767
|
+
border-left: 1px solid var(--surface-3);
|
|
4768
|
+
user-select: none;
|
|
4769
|
+
}
|
|
4770
|
+
.twm-zoom__step {
|
|
4771
|
+
display: inline-flex;
|
|
4772
|
+
align-items: center;
|
|
4773
|
+
justify-content: center;
|
|
4774
|
+
width: 15px;
|
|
4775
|
+
height: 15px;
|
|
4776
|
+
padding: 0;
|
|
4777
|
+
border: none;
|
|
4778
|
+
border-radius: 2px;
|
|
4779
|
+
background: none;
|
|
4780
|
+
color: var(--text-muted);
|
|
4781
|
+
font: inherit;
|
|
4782
|
+
font-size: var(--font-size-sm);
|
|
4783
|
+
line-height: 1;
|
|
4784
|
+
cursor: pointer;
|
|
4785
|
+
}
|
|
4786
|
+
.twm-zoom__step:hover { background: var(--surface-3); color: var(--text-primary); }
|
|
4787
|
+
/* A short track on purpose: it sits between two buttons in a thin bar,
|
|
4788
|
+
and the readout — not the thumb position — answers "where am I". */
|
|
4789
|
+
.twm-zoom__slider {
|
|
4790
|
+
width: 78px;
|
|
4791
|
+
height: 12px;
|
|
4792
|
+
margin: 0;
|
|
4793
|
+
cursor: pointer;
|
|
4794
|
+
accent-color: var(--accent-bright);
|
|
4795
|
+
}
|
|
4796
|
+
/* Tabular numerals, so the readout does not jiggle between 95% and 100%
|
|
4797
|
+
while the thumb moves. */
|
|
4798
|
+
.twm-zoom__value {
|
|
4799
|
+
min-width: 34px;
|
|
4800
|
+
padding: 0;
|
|
4801
|
+
border: none;
|
|
4802
|
+
background: none;
|
|
4803
|
+
color: var(--text-muted);
|
|
4804
|
+
font-family: var(--font-family-mono);
|
|
4805
|
+
font-size: var(--font-size-xs);
|
|
4806
|
+
font-variant-numeric: tabular-nums;
|
|
4807
|
+
text-align: right;
|
|
4808
|
+
cursor: pointer;
|
|
3439
4809
|
}
|
|
4810
|
+
.twm-zoom__value:hover { color: var(--text-primary); }
|