monowind 0.1.3 → 0.1.5

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/src/styles.css CHANGED
@@ -8,6 +8,10 @@
8
8
  * ENGINE owns (geometry, typography locking, alignment/overflow enforcement)
9
9
  * stay unlayered with `!important` so no utility can push content off-grid. */
10
10
 
11
+ /* Gap-decoration utilities + engine mirrors (specs/gap-decorations.md).
12
+ * Must precede all rules, per @import syntax. */
13
+ @import "./rules.css";
14
+
11
15
  @layer base {
12
16
  mono-wind {
13
17
  display: block;
@@ -17,10 +21,16 @@
17
21
  isolation: isolate;
18
22
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
19
23
  font-size: 14px;
20
- line-height: 1;
24
+ /* `normal` picks a line box tall enough for the font's ascent +
25
+ * descent so inline span backgrounds fit within a row instead of
26
+ * bleeding into the next. Cell height derives from the probe, so
27
+ * this stays deterministic — just taller. */
28
+ line-height: normal;
21
29
  letter-spacing: 0;
22
- /* Default themable focus ring color (see form-controls rule below). */
23
- --mw-focus-color: currentColor;
30
+ /* Themable fg / bg pair for the focus-invert default. System
31
+ * colors honor the viewer's color scheme; authors override. */
32
+ --mw-fg: canvastext;
33
+ --mw-bg: canvas;
24
34
  }
25
35
 
26
36
  /* Normalize native form controls so they don't borrow browser chrome that
@@ -37,14 +47,81 @@
37
47
  appearance: none;
38
48
  }
39
49
 
40
- /* Restore a themable focus ring — `appearance: none` above kills the UA
41
- * default, and without this keyboard users can lose track of where focus
42
- * is. `outline-offset: -1px` keeps the ring inside the element's box so it
43
- * doesn't overlap our decoration layer. */
44
- mono-wind :is(button, input, select, textarea, a):focus-visible {
45
- outline: 2px solid var(--mw-focus-color);
46
- outline-offset: -1px;
50
+ /* Form controls render their value/caret/selection natively — the
51
+ * color-transparent rule below excludes them via `:not()`, so this
52
+ * visible color and themed caret actually stick. */
53
+ mono-wind :is(input, textarea, select) {
54
+ color: var(--mw-fg);
55
+ caret-color: var(--mw-fg);
56
+ }
57
+
58
+ /* Placeholder at half the element's own color, so it tracks both the
59
+ * theme and the focus-invert. Explicit opacity: Firefox dims its UA
60
+ * placeholder with opacity, which would double with the color mix. */
61
+ mono-wind :is(input, textarea)::placeholder {
62
+ color: color-mix(in srgb, currentcolor 50%, transparent);
63
+ opacity: 1;
64
+ }
65
+
66
+ /* A required <select> still on its empty placeholder option matches
67
+ * :invalid — same placeholder treatment (the :invalid specificity
68
+ * beats the form-control color above; the pair below beats the
69
+ * focus-invert). currentcolor would self-refer here, so the themed
70
+ * colors are spelled out. */
71
+ mono-wind select:invalid {
72
+ color: color-mix(in srgb, var(--mw-fg) 50%, transparent);
73
+ }
74
+ mono-wind select:invalid:focus-visible {
75
+ color: color-mix(in srgb, var(--mw-bg) 50%, transparent);
76
+ }
77
+
78
+ /* Single-line controls: never wrap, clip overflow. The one-row
79
+ * floor lives in the tree builder (a CSS min-height on the outer
80
+ * box gives no content-area guarantee once border + padding exceed
81
+ * it). */
82
+ mono-wind :is(input, select) {
83
+ white-space: nowrap;
84
+ overflow: hidden;
47
85
  }
86
+
87
+ /* Textarea: engine controls the size (it auto-grows to fit the
88
+ * value, so scrolling never has anything to reveal) — the native
89
+ * resize handle and scrollbars would drag the box off-grid. `clip`
90
+ * (not `hidden`) so the caret can't scroll the box either: under
91
+ * `leading-*` the last line's half-leading is a phantom scroll
92
+ * range that would shift every line off its row. */
93
+ mono-wind textarea {
94
+ resize: none;
95
+ overflow: clip;
96
+ }
97
+
98
+ /* :focus-visible on ANY element: TUI-native invert. Outlines off
99
+ * (they'd clip against grid glyphs). Where each declaration lands:
100
+ * bg only shows via the grid (engine reads it during [measuring];
101
+ * the light-DOM bg is transparent-locked below); color/caret-color
102
+ * paint on editables (they're the ones exempted from the
103
+ * color-transparent lock), and get grid-mirrored by the engine for
104
+ * everyone else. */
105
+ mono-wind :focus-visible {
106
+ outline: none;
107
+ background-color: var(--mw-fg);
108
+ color: var(--mw-bg);
109
+ caret-color: var(--mw-bg);
110
+ }
111
+ }
112
+
113
+ /* === Modes and selection (engine-owned from here down) =============== */
114
+
115
+ /* select="grid": pass pointer events through non-interactive light-DOM
116
+ * elements so drag lands on the grid <pre> (native drag-selection of
117
+ * the ASCII); interactives opt back into `auto` so clicks still work.
118
+ * (The slot itself is pointer-events: none in the shadow CSS.) */
119
+ mono-wind[select="grid"] * {
120
+ pointer-events: none !important;
121
+ }
122
+ mono-wind[select="grid"]
123
+ :is(a, button, input, select, textarea, label, [tabindex], [role="button"]) {
124
+ pointer-events: auto !important;
48
125
  }
49
126
 
50
127
  /* Hide the host until the first layout completes to avoid the flash of raw
@@ -55,13 +132,30 @@ mono-wind:not([data-mw-ready]) {
55
132
  visibility: hidden !important;
56
133
  }
57
134
 
135
+ /* Full invert on selection. Both fields spelled out — setting only
136
+ * `color` makes some engines drop the OS default background. Native
137
+ * copy still yields the real text (the DOM the browser reads). The
138
+ * shadow grid mirrors this rule inside element.ts. */
139
+ mono-wind:not([measuring]) *::selection {
140
+ color: var(--mw-bg, canvas) !important;
141
+ background: var(--mw-fg, canvastext) !important;
142
+ }
143
+ /* Focus-invert already flipped the element's colors, so re-flipping in
144
+ * ::selection would land on the same colors as the focused bg and hide
145
+ * the selection. Swap them back for the focused element. */
146
+ mono-wind:not([measuring]) :focus-visible::selection {
147
+ color: var(--mw-fg, canvastext) !important;
148
+ background: var(--mw-bg, canvas) !important;
149
+ }
150
+
151
+ /* === Typography locks and measuring gates ============================ */
152
+
58
153
  /* Lock the font on descendants so the grid metrics stay consistent —
59
154
  * `!important` because user classes (`.text-2xl` etc.) would otherwise beat
60
155
  * these element-selector rules on specificity. */
61
156
  mono-wind * {
62
157
  font-family: inherit !important;
63
158
  font-size: inherit !important;
64
- vertical-align: baseline !important;
65
159
  }
66
160
 
67
161
  /* Grid-template read assist (specs/grid.md), no-Typed-OM engines only
@@ -88,10 +182,21 @@ mono-wind[measuring] [data-mw-degrid] {
88
182
  * laid-out ancestor (which sets the row multiple below) — `leading-*` on
89
183
  * inline elements is ignored by design. */
90
184
  mono-wind:not([measuring]) * {
185
+ /* Baseline-shift lock (cell-model): gated like the rest, so the style
186
+ * reader sees the authored/UA vertical-align (table cells read it). */
187
+ vertical-align: baseline !important;
91
188
  white-space: normal !important;
92
189
  overflow-wrap: anywhere !important;
93
190
  letter-spacing: calc(var(--mw-rls, 0px) + var(--mw-ls) * var(--mw-cw, 1px)) !important;
94
191
  line-height: inherit !important;
192
+ /* Backgrounds paint in the grid too, so the light DOM's own bg goes
193
+ * transparent — otherwise it would cover the grid (light DOM sits
194
+ * ON TOP of the grid in the shadow tree so native selection
195
+ * highlights can show through). The engine still reads authored
196
+ * background-color during [measuring] (this rule is off then), so
197
+ * bg-* and focus-invert both make it into the grid paint. */
198
+ background-color: transparent !important;
199
+ text-shadow: none !important;
95
200
  /* Inline-element horizontal padding, quantized: the engine writes
96
201
  * `--mw-ipl`/`--mw-ipr` (whole cells) on inline elements whose authored
97
202
  * padding it reserved in the run; everything else gets 0, so raw
@@ -103,6 +208,14 @@ mono-wind:not([measuring]) * {
103
208
  padding-right: calc(var(--mw-ipr, 0) * var(--mw-cw, 1px)) !important;
104
209
  }
105
210
 
211
+ /* Light-DOM text goes invisible so the grid is the only visible copy;
212
+ * form controls opt out because they render their value/caret/selection
213
+ * natively (the tree builder skips their content so the grid doesn't
214
+ * double-render). Events, ARIA, focus, selection all still work. */
215
+ mono-wind:not([measuring]) *:not(input):not(textarea):not(select) {
216
+ color: transparent !important;
217
+ }
218
+
106
219
  /* Author-requested `white-space: nowrap` (or `pre`) — the engine sized this
107
220
  * box for unwrapped hard lines, so the browser must not wrap either. Wins
108
221
  * over the lock above on specificity. */
@@ -128,6 +241,8 @@ mono-wind:not([measuring]) [data-mw-nowrap][data-mw-clip] {
128
241
  overflow: hidden !important;
129
242
  }
130
243
 
244
+ /* === Engine-owned geometry =========================================== */
245
+
131
246
  /* Every CSS property the engine has taken over on laid-out elements.
132
247
  * Gated on the `measuring` attribute so the style reader still sees the
133
248
  * authored values during its read pass. Being unlayered already beats all
@@ -139,7 +254,7 @@ mono-wind:not([measuring]) [data-mw-nowrap][data-mw-clip] {
139
254
  * Geometry: position/box-sizing/left/top/width/height + min/max — set from
140
255
  * the engine-owned --mw-x/y/w/h custom properties.
141
256
  *
142
- * Neutralized: border-width (painted as glyphs by the decoration layer),
257
+ * Neutralized: border-width (painted as glyphs by the shadow grid),
143
258
  * margin (position:absolute + margin still nudges the element off-grid).
144
259
  *
145
260
  * Owned via `--mw-p{t,r,b,l}` (padding) and `--mw-b{t,r,b,l}` (border cells):
@@ -203,6 +318,16 @@ mono-wind:not([measuring]) [data-mw-inline-box] {
203
318
  max-width: none !important;
204
319
  max-height: none !important;
205
320
  border-width: 0 !important;
321
+ /* Gap rules are engine-painted glyphs (specs/gap-decorations.md);
322
+ * css-gaps browsers must not also paint their native (fractional)
323
+ * rules — e.g. between an emptied grid's explicit tracks. */
324
+ column-rule-style: none !important;
325
+ row-rule-style: none !important;
326
+ /* Authored z-index applies only where CSS says (positioned, flex/grid
327
+ * items) — absolutization would otherwise activate it everywhere. The
328
+ * engine writes --mw-z in exactly those cases; without it the calc()
329
+ * is invalid at computed-value time and z-index falls back to auto. */
330
+ z-index: calc(var(--mw-z)) !important;
206
331
  padding-top: calc((var(--mw-pt, 0) + var(--mw-bt, 0)) * var(--mw-ch, 1px)) !important;
207
332
  padding-right: calc(
208
333
  max(0, var(--mw-pr, 0) + var(--mw-br, 0) - var(--mw-ls, 0)) * var(--mw-cw, 1px)
@@ -224,6 +349,18 @@ mono-wind:not([measuring]) [data-mw-inline-box] {
224
349
  mono-wind:not([measuring]) [data-mw-inline-box] {
225
350
  position: relative !important;
226
351
  vertical-align: top !important;
352
+ /* Same half-leading cancellation as laid-out elements — without it
353
+ * an inline box's NATIVE ink (a textarea's value under leading-*)
354
+ * sits centered in its line boxes instead of on its rows. */
355
+ translate: 0 calc(var(--mw-lhs, 0) * var(--mw-ch, 1px)) !important;
356
+ }
357
+
358
+ /* Authored `vertical-align: bottom` on an atomic inline box: grid-exact
359
+ * in every engine (probed), so it passes through; the engine drops the
360
+ * line's text to the box's last row to match. middle/baseline are
361
+ * off-grid and behave as top (cell-model deviation). */
362
+ mono-wind:not([measuring]) [data-mw-inline-box][data-mw-vbottom] {
363
+ vertical-align: bottom !important;
227
364
  }
228
365
 
229
366
  /* Author-requested `overflow: hidden` or `overflow: clip` — both keep
@@ -249,6 +386,8 @@ mono-wind:not([measuring]) [data-mw-inline-inset] {
249
386
  left: calc(var(--mw-il) * var(--mw-cw, 1px)) !important;
250
387
  }
251
388
 
389
+ /* === Engine state attributes (warnings, hiding, alignment) =========== */
390
+
252
391
  /* A container whose direct text was dropped (mixed text + block children,
253
392
  * cell-model deviation): the browser would paint that text unpositioned
254
393
  * over the laid-out children, so hide it. `visibility` is inherited AND
@@ -264,9 +403,15 @@ mono-wind:not([measuring]) [data-mw-dropped-text] > [data-mw-laid-out] {
264
403
  visibility: visible !important;
265
404
  }
266
405
 
406
+ /* Misparented table content (no anonymous boxes, specs/table.md) and
407
+ * <col>/<colgroup> boxes: fully hidden, subtree included. */
408
+ mono-wind:not([measuring]) [data-mw-table-hidden] {
409
+ display: none !important;
410
+ }
411
+
267
412
  /* Force off-grid text alignments back to `start`. `text-left`/`right`/`start`/
268
413
  * `end` all land on whole cells (offset = (W - N) × cell width); `center` and
269
414
  * `justify` produce fractional per-line offsets we can't snap to the grid. */
270
- mono-wind [data-mw-text-align-blocked] {
415
+ mono-wind:not([measuring]) [data-mw-text-align-blocked] {
271
416
  text-align: start !important;
272
417
  }
package/dist/ascii.d.ts DELETED
@@ -1,16 +0,0 @@
1
- import type { LayoutNode } from "./types.ts";
2
- /**
3
- * Render a laid-out tree as plain-text ASCII art: borders as box-drawing
4
- * glyphs, leaf text word-wrapped inside its content box, everything else as
5
- * spaces.
6
- *
7
- * This is the engine's "screenshot without a browser": deterministic,
8
- * font-independent, and diffable — used for golden regression tests and as
9
- * a debugging/agent-inspection tool. It intentionally renders geometry the
10
- * way the browser would paint it (same border-run and word-wrap code), minus
11
- * colors and fonts.
12
- *
13
- * Content that overflows the root box is clipped at the grid edges.
14
- */
15
- export declare function renderAscii(root: LayoutNode): string;
16
- //# sourceMappingURL=ascii.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ascii.d.ts","sourceRoot":"","sources":["../src/ascii.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAepD"}