Scale stage 1: everything the UI sizes with is now rem
Groundwork for the continuous interface scale (a root font-size lever): every size the UI derives from must be root-relative, or it freezes while the rest grows. Gate for this commit: at the default 16px root the app renders pixel-identical to before — every conversion is exact (11px = 0.6875rem …). - The 8 panel-chrome tokens in styles.css: px -> rem, and the data-text compact/large tiers are DELETED — a continuous scale replaces discrete tiers. Same for --w7-fs (12px -> 0.75rem) and the win7 --radius (5px -> 0.3125rem, still landing rounded-md exactly on --w7-el-bdr's 3px). --radius-sm/md subtrahends: px -> rem, or corners would sharpen as the UI grew. - All 93 text-[11px]/text-[10px] arbitraries -> rem equivalents, plus the two inline padding:24px empty-state cells in the 7like file panel. - 7like.css gains a scale bridge in skinfix: 7.css is 370 px values with zero rem, and --w7-font reaches only 15 rules' TYPOGRAPHY — under a root lever win7 text would grow inside boxes frozen at 23px/14px/15px. The bridge re-expresses 7.css's size vars and its hardcoded box metrics (buttons via .w7-btn only, inputs, table rows/headers, title-bar controls, menu items, tabs, the 10pt check glyph) in rem at identical scale-1 pixels. 1px hairlines and focus rects stay px deliberately — crisp at any scale, and the tr border exists precisely to stop selection shifting rows. - Two skinfix traps found while writing it, one by reasoning and one by pixels: (a) NO blanket `.win7 button` restatement — skinfix outranks the skin-layer neutralizer that strips 7.css's 75x23 metrics off the ~84 icon buttons, so a blanket rule would re-inflate them all; (b) restating a base rule in skinfix also outranks 7.css's own VARIANTS of it by layer — the title-bar rule silently shrank the close button from 48px to 29px until the harness pixel diff caught it, and the active tab's padding-bottom needed the same treatment. Both variants are restated alongside their base rules. - TS px constants that mirror rem CSS are the latent-bug half of this work: both context menus' flip/clamp math converts through a new @core/metrics remPx() helper reading the LIVE root font-size (the shadlike menu's MENU_W=224 diverged from its w-56 box the moment scale != 1; the 7like menu's [style.width.px] pinned the box at 200px while text grew — it now binds width in rem, so math and box cannot disagree). The editors' cachedCharWidth is now keyed on the computed font string, self-invalidating under any font change, and both || px fallbacks derive from the element's font size instead of assuming a 16px root. Verified: tsc + build clean; modern and win7 live captures and the harness all diff to zero against pre-change baselines in their opaque regions (the harness to exactly zero end to end; modern's only delta is the native GTK titlebar focus state; win7's is the translucent Aero corner over the desktop). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,18 @@
|
||||
import { Component, ElementRef, computed, effect, inject, signal, viewChild } from '@angular/core';
|
||||
import { ContextMenuService, type MenuItem } from '@core/context-menu.service';
|
||||
import { FocusService } from '@core/focus.service';
|
||||
import { remPx } from '@core/metrics';
|
||||
|
||||
/** Rough per-item metrics, used only to decide whether the menu must flip. */
|
||||
const ITEM_H = 24; // 7.css: 4px padding x2 + ~15px line
|
||||
const DIVIDER_H = 7; // .has-divider:after: 2px bevel + margins
|
||||
const MENU_W = 200;
|
||||
const EDGE_GAP = 6;
|
||||
/** Rough per-item metrics in REM, used to size the menu and decide whether it
|
||||
* must flip. Rem because the interface scale is a root font-size lever: the
|
||||
* old `MENU_W = 200` was pinned into the DOM via [style.width.px], so the box
|
||||
* stayed 200px while the text inside grew with the scale and overflowed.
|
||||
* Values mirror the skinfix restatement of 7.css's menu metrics in 7like.css
|
||||
* (item padding 0.25rem x2 + the ~0.9375rem line). */
|
||||
const ITEM_H_REM = 1.5; // 24px @16
|
||||
const DIVIDER_H_REM = 0.4375; // 7px @16: 2px bevel + 3px/2px margins
|
||||
const MENU_W_REM = 12.5; // 200px @16
|
||||
const EDGE_GAP = 6; // px on purpose: a viewport-edge margin, not a UI size
|
||||
|
||||
/** One rendered <li>: separators collapse onto the item BEFORE them. */
|
||||
interface MenuRow {
|
||||
@@ -44,7 +50,7 @@ interface MenuRow {
|
||||
(keydown)="onKeydown($event)"
|
||||
[style.left.px]="position().x"
|
||||
[style.top.px]="position().y"
|
||||
[style.width.px]="menuWidth"
|
||||
[style.width.rem]="menuWidthRem"
|
||||
class="fixed z-50 outline-none"
|
||||
>
|
||||
@for (row of rows(); track $index) {
|
||||
@@ -80,7 +86,9 @@ export class ContextMenu {
|
||||
private readonly focus = inject(FocusService);
|
||||
private readonly menuEl = viewChild<ElementRef<HTMLUListElement>>('menuEl');
|
||||
|
||||
protected readonly menuWidth = MENU_W;
|
||||
/** Rendered width AND math input are the same rem value, so the flip point
|
||||
* and the box can never disagree — the shadlike menu's old bug. */
|
||||
protected readonly menuWidthRem = MENU_W_REM;
|
||||
|
||||
/** Model items → rendered rows: a separator becomes has-divider on its predecessor. */
|
||||
readonly rows = computed<MenuRow[]>(() => {
|
||||
@@ -96,15 +104,20 @@ export class ContextMenu {
|
||||
return rows;
|
||||
});
|
||||
|
||||
/** Click point, flipped/clamped so the menu never overflows the viewport. */
|
||||
/** Click point, flipped/clamped so the menu never overflows the viewport.
|
||||
* UI metrics convert through the live root font-size (remPx); the click
|
||||
* point and window.inner* are both viewport CSS px already. */
|
||||
readonly position = computed(() => {
|
||||
const req = this.menu.request();
|
||||
if (!req) return { x: 0, y: 0 };
|
||||
const rows = this.rows();
|
||||
const menuW = remPx(MENU_W_REM);
|
||||
const height =
|
||||
rows.length * ITEM_H + rows.filter((r) => r.hasDivider).length * DIVIDER_H + 6;
|
||||
rows.length * remPx(ITEM_H_REM) +
|
||||
rows.filter((r) => r.hasDivider).length * remPx(DIVIDER_H_REM) +
|
||||
remPx(0.25); // ul padding 0.125rem x2
|
||||
const x =
|
||||
req.x + MENU_W > window.innerWidth - EDGE_GAP ? Math.max(EDGE_GAP, req.x - MENU_W) : req.x;
|
||||
req.x + menuW > window.innerWidth - EDGE_GAP ? Math.max(EDGE_GAP, req.x - menuW) : req.x;
|
||||
const y =
|
||||
req.y + height > window.innerHeight - EDGE_GAP ? Math.max(EDGE_GAP, req.y - height) : req.y;
|
||||
return { x, y };
|
||||
|
||||
Reference in New Issue
Block a user