@ashley-shrok/viewmodel-shell 0.4.6 → 0.4.8
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/dist/tui.js +42 -14
- package/package.json +1 -1
package/dist/tui.js
CHANGED
|
@@ -989,6 +989,7 @@ function App(props) {
|
|
|
989
989
|
focusKey: (o) => map.get(o),
|
|
990
990
|
interactive,
|
|
991
991
|
fill: fillViewport,
|
|
992
|
+
fillCols: fillViewport ? vp.cols : undefined,
|
|
992
993
|
draft: (k) => draftFor(k),
|
|
993
994
|
onFieldChange: (k, v) => setDraft((s) => ({ ...s, [k]: v })),
|
|
994
995
|
onFieldSubmit,
|
|
@@ -1244,29 +1245,52 @@ export class TuiAdapter {
|
|
|
1244
1245
|
}
|
|
1245
1246
|
}
|
|
1246
1247
|
/** page & section share the 4 layout presets. Information-honest, not pixel. */
|
|
1247
|
-
layoutContainer(layout, children, density, rctx) {
|
|
1248
|
+
layoutContainer(layout, children, density, rctx, topLevel = false) {
|
|
1248
1249
|
const sp = this.spacing(density);
|
|
1249
1250
|
const kids = (children ?? []).map((c, i) => this.renderNode(c, this.keyOf(c, i), density, undefined, rctx));
|
|
1250
|
-
// 0.4.
|
|
1251
|
-
// terminal width
|
|
1252
|
-
//
|
|
1253
|
-
//
|
|
1254
|
-
//
|
|
1255
|
-
//
|
|
1256
|
-
|
|
1251
|
+
// 0.4.7 — numeric width ONLY at the page's TOP layout container (it IS
|
|
1252
|
+
// the full terminal width). Everything below fills via Yoga align-stretch
|
|
1253
|
+
// from this numeric anchor (proven on the real-adapter oracle). NESTED
|
|
1254
|
+
// layoutContainer calls (a section's own children) pass topLevel=false ⇒
|
|
1255
|
+
// no width ⇒ they get the correct PANE-relative width via stretch (a
|
|
1256
|
+
// global numeric cols here would overflow a narrower pane). `cards` never
|
|
1257
|
+
// gets it (uniform small-tile grid by design). Not top-level / not
|
|
1258
|
+
// filling ⇒ {} ⇒ byte-identical.
|
|
1259
|
+
const fillW = topLevel && rctx.fillCols ? { width: rctx.fillCols } : {};
|
|
1257
1260
|
switch (layout) {
|
|
1258
1261
|
case "split":
|
|
1259
|
-
return (_jsx(Box, { flexDirection: "row", gap: sp.gap || 1, ...fillW, children: kids.map((el, i) => (_jsx(Box, { flexGrow: 1, flexShrink: 1, flexBasis: 0, children:
|
|
1262
|
+
return (_jsx(Box, { flexDirection: "row", gap: sp.gap || 1, ...fillW, children: kids.map((el, i) => (_jsx(Box, { flexGrow: 1, flexShrink: 1, flexBasis: 0, children: el }, i))) }));
|
|
1260
1263
|
case "cards":
|
|
1261
1264
|
return (_jsx(Box, { flexDirection: "row", flexWrap: "wrap", gap: sp.gap || 1, children: kids.map((el, i) => (_jsx(Box, { flexGrow: 0, flexShrink: 1, flexBasis: 28, minWidth: 20, children: el }, i))) }));
|
|
1262
1265
|
case "sidebar": {
|
|
1263
1266
|
if (kids.length === 0)
|
|
1264
1267
|
return _jsx(Box, {});
|
|
1265
1268
|
const [rail, ...rest] = kids;
|
|
1269
|
+
// Deterministic NUMERIC split when filling. `flexGrow` does NOT
|
|
1270
|
+
// distribute reliably past a flexShrink:0 fixed-basis rail (real-
|
|
1271
|
+
// adapter oracle: sidebar stayed 34 at cols 80 AND 160, while `split`
|
|
1272
|
+
// — symmetric flexGrow — scaled). So when filling: fixed numeric rail
|
|
1273
|
+
// + the exact remaining width on the main pane (it then carries the
|
|
1274
|
+
// terminal width to its section via align-stretch). Not filling ⇒ the
|
|
1275
|
+
// ORIGINAL flex props, byte-identical.
|
|
1276
|
+
const cols = topLevel && rctx.fillCols ? rctx.fillCols : 0;
|
|
1277
|
+
const filling = cols > 0;
|
|
1278
|
+
const RAIL = 24;
|
|
1279
|
+
const g = sp.gap || 1;
|
|
1280
|
+
const mw = Math.max(1, cols - RAIL - g);
|
|
1266
1281
|
if (rest.length === 0) {
|
|
1267
|
-
return
|
|
1282
|
+
return filling ? (
|
|
1283
|
+
// flexDirection:column ⇒ the lone section align-stretches to the
|
|
1284
|
+
// numeric width (a default-row box would not width-stretch it).
|
|
1285
|
+
_jsx(Box, { flexDirection: "column", flexShrink: 0, ...fillW, children: rail })) : (_jsx(Box, { flexShrink: 0, flexBasis: 24, minWidth: 18, children: rail }));
|
|
1268
1286
|
}
|
|
1269
|
-
return (_jsxs(Box, { flexDirection: "row", gap:
|
|
1287
|
+
return (_jsxs(Box, { flexDirection: "row", gap: g, ...fillW, children: [filling ? (_jsx(Box, { flexShrink: 0, width: RAIL, children: rail })) : (_jsx(Box, { flexShrink: 0, flexBasis: 24, minWidth: 18, children: rail })), filling ? (
|
|
1288
|
+
// Single numeric-width column directly holding the detail
|
|
1289
|
+
// sections — exactly the `stack` shape the oracle proves scales
|
|
1290
|
+
// (a card section align-stretches to a numeric-width parent).
|
|
1291
|
+
// The extra auto-width wrapper that used to sit here broke that
|
|
1292
|
+
// align-stretch chain (oracle: card detail stuck at 38).
|
|
1293
|
+
_jsx(Box, { flexDirection: "column", gap: sp.gap, flexShrink: 1, width: mw, children: rest })) : (_jsx(Box, { flexGrow: 1, flexShrink: 1, flexBasis: 0, children: _jsx(Box, { flexDirection: "column", gap: sp.gap, children: rest }) }))] }));
|
|
1270
1294
|
}
|
|
1271
1295
|
default: // "stack" / undefined
|
|
1272
1296
|
return (_jsx(Box, { flexDirection: "column", gap: sp.gap, ...fillW, children: kids }));
|
|
@@ -1278,7 +1302,7 @@ export class TuiAdapter {
|
|
|
1278
1302
|
case "page": {
|
|
1279
1303
|
const d = node.density === "compact" ? "compact" : "comfortable";
|
|
1280
1304
|
const sp = this.spacing(d);
|
|
1281
|
-
return (_jsxs(Box, { flexDirection: "column", gap: sp.gap, ...(rctx.
|
|
1305
|
+
return (_jsxs(Box, { flexDirection: "column", gap: sp.gap, ...(rctx.fillCols ? { width: rctx.fillCols, flexGrow: 1 } : {}), children: [node.title ? (_jsx(Box, { children: _jsx(Text, { bold: true, underline: true, children: node.title }) })) : null, this.layoutContainer(node.layout, node.children ?? [], d, rctx, true)] }, key));
|
|
1282
1306
|
}
|
|
1283
1307
|
case "section": {
|
|
1284
1308
|
const sp = this.spacing(density);
|
|
@@ -1304,10 +1328,14 @@ export class TuiAdapter {
|
|
|
1304
1328
|
if (!href) {
|
|
1305
1329
|
return (_jsx(Box, { children: _jsx(Text, { underline: true, color: inherited?.color, dimColor: inherited?.dim, children: node.label }) }, key));
|
|
1306
1330
|
}
|
|
1307
|
-
// OSC 8 hyperlink
|
|
1331
|
+
// OSC 8 hyperlink: ESC ]8;; <uri> BEL <label> ESC ]8;; BEL. The
|
|
1332
|
+
// ESC introducer + BEL terminator are LOAD-BEARING — without them
|
|
1333
|
+
// (a long-latent bug fixed in 0.4.8) this is plain `]8;;…` garbage
|
|
1334
|
+
// text in every terminal. `\x1b`/`\x07` escapes match osc52()'s
|
|
1335
|
+
// proven style. As the SOLE child of its own Box with no wrap, the
|
|
1308
1336
|
// string-width over-count (string-width does not strip OSC 8) stays
|
|
1309
1337
|
// contained to this line and cannot corrupt sibling layout.
|
|
1310
|
-
const osc =
|
|
1338
|
+
const osc = `\x1b]8;;${node.href}\x07${node.label}\x1b]8;;\x07${copied ? " ✓ copied" : ""}`;
|
|
1311
1339
|
return this.focusWrap(_jsx(Box, { children: _jsx(Text, { wrap: "truncate-end", underline: true, color: inherited?.color, dimColor: inherited?.dim, children: osc }) }, key), focused, key);
|
|
1312
1340
|
}
|
|
1313
1341
|
case "stat-bar":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashley-shrok/viewmodel-shell",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "A server-driven UI framework where the wire format is structured enough that agents can build full-stack apps without ever opening a browser and all UI tests are pure unit tests with no browser runtime. Server returns a JSON tree of typed nodes; a thin TypeScript adapter renders it to DOM. Backend-agnostic — a .NET reference backend ships with the repo, but any language can produce the JSON contract.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|