vintage-frames 0.1.1 → 0.2.1

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.
Files changed (50) hide show
  1. package/custom-elements.json +348 -40
  2. package/dist/components/vf-button.d.ts +1 -1
  3. package/dist/components/vf-checkbox.d.ts +5 -0
  4. package/dist/components/vf-checkbox.js +18 -10
  5. package/dist/components/vf-container.d.ts +2 -2
  6. package/dist/components/vf-desktop.d.ts +1 -1
  7. package/dist/components/vf-fieldset.d.ts +1 -1
  8. package/dist/components/vf-fieldset.js +6 -1
  9. package/dist/components/vf-grid.d.ts +1 -1
  10. package/dist/components/vf-icon.d.ts +1 -1
  11. package/dist/components/vf-img.d.ts +1 -1
  12. package/dist/components/vf-label.d.ts +1 -1
  13. package/dist/components/vf-list-item.d.ts +9 -1
  14. package/dist/components/vf-list-item.js +15 -14
  15. package/dist/components/vf-list.d.ts +1 -1
  16. package/dist/components/vf-menu-bar.d.ts +1 -1
  17. package/dist/components/vf-menu-item.d.ts +10 -1
  18. package/dist/components/vf-menu-item.js +35 -34
  19. package/dist/components/vf-menu.d.ts +11 -2
  20. package/dist/components/vf-menu.js +33 -32
  21. package/dist/components/vf-option.d.ts +11 -1
  22. package/dist/components/vf-option.js +16 -15
  23. package/dist/components/vf-paragraph.d.ts +3 -3
  24. package/dist/components/vf-progress-bar.d.ts +1 -1
  25. package/dist/components/vf-radio.d.ts +5 -0
  26. package/dist/components/vf-radio.js +16 -11
  27. package/dist/components/vf-scroll-area.d.ts +1 -1
  28. package/dist/components/vf-select.d.ts +1 -1
  29. package/dist/components/vf-separator.d.ts +1 -1
  30. package/dist/components/vf-slider.d.ts +1 -1
  31. package/dist/components/vf-stack.d.ts +27 -9
  32. package/dist/components/vf-stack.js +30 -8
  33. package/dist/components/vf-swatch.d.ts +1 -1
  34. package/dist/components/vf-window.d.ts +1 -1
  35. package/dist/cross-center.d.ts +76 -0
  36. package/dist/cross-center.js +69 -0
  37. package/dist/define.d.ts +2 -2
  38. package/dist/define.js +1 -1
  39. package/dist/grid-snap.d.ts +2 -24
  40. package/dist/grid-snap.js +7 -33
  41. package/dist/index.d.ts +5 -5
  42. package/dist/index.js +50 -50
  43. package/dist/position.d.ts +11 -2
  44. package/dist/styles/recipes/host.js +14 -1
  45. package/dist/styles/recipes/toggle.js +24 -0
  46. package/dist/text-control.d.ts +1 -1
  47. package/docs/SPEC.md +10 -9
  48. package/editor/vscode.html-custom-data.json +50 -10
  49. package/editor/web-types.json +96 -16
  50. package/package.json +1 -3
@@ -322,7 +322,7 @@
322
322
  "privacy": "private",
323
323
  "readonly": true,
324
324
  "default": "new GridSnapController(this)",
325
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
325
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
326
326
  },
327
327
  {
328
328
  "kind": "field",
@@ -1144,7 +1144,7 @@
1144
1144
  ],
1145
1145
  "slots": [
1146
1146
  {
1147
- "description": "The label, rendered to the right of the box with a 6px gap.",
1147
+ "description": "The label, rendered to the right of the box with a 6px gap. Left empty, the label and its gap collapse: the control is the bare 13×13 box.",
1148
1148
  "name": ""
1149
1149
  }
1150
1150
  ],
@@ -1185,6 +1185,16 @@
1185
1185
  "description": "Value submitted with the form while checked.",
1186
1186
  "attribute": "value"
1187
1187
  },
1188
+ {
1189
+ "kind": "field",
1190
+ "name": "_hasLabel",
1191
+ "type": {
1192
+ "text": "boolean"
1193
+ },
1194
+ "privacy": "private",
1195
+ "default": "false",
1196
+ "description": "Whether the slot holds non-whitespace content; gates the label row."
1197
+ },
1188
1198
  {
1189
1199
  "kind": "field",
1190
1200
  "name": "valueMissing",
@@ -1241,6 +1251,24 @@
1241
1251
  "module": "src/toggle-control.ts"
1242
1252
  }
1243
1253
  },
1254
+ {
1255
+ "kind": "method",
1256
+ "name": "#onLabelSlotChange",
1257
+ "privacy": "private",
1258
+ "return": {
1259
+ "type": {
1260
+ "text": "void"
1261
+ }
1262
+ },
1263
+ "parameters": [
1264
+ {
1265
+ "name": "event",
1266
+ "type": {
1267
+ "text": "Event"
1268
+ }
1269
+ }
1270
+ ]
1271
+ },
1244
1272
  {
1245
1273
  "kind": "method",
1246
1274
  "name": "formResetCallback",
@@ -1355,7 +1383,7 @@
1355
1383
  "privacy": "private",
1356
1384
  "readonly": true,
1357
1385
  "default": "new GridSnapController(this)",
1358
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts.",
1386
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts.",
1359
1387
  "inheritedFrom": {
1360
1388
  "name": "VfToggleControl",
1361
1389
  "module": "src/toggle-control.ts"
@@ -1946,7 +1974,7 @@
1946
1974
  "declarations": [
1947
1975
  {
1948
1976
  "kind": "class",
1949
- "description": "`<vf-container>` — a box that is nothing but its declared size.\n\nThe kit's positioned-placement story (src/position.ts) ends with one line of\nCSS it can't write for you: children placed with `top`/`left` need a\npositioned ancestor, and while every kit container is one — a window body, a\nstack, a fieldset — a region of *your own* needs `position: relative` in a\nstylesheet. This component is that region as an element: declare `width` and\n`height` in whole system px, slot anything into it, place children against\nits origin. A DITL's enclosing rectangle, with nothing drawn in it.\n\n```html\n<vf-container width=\"200\" height=\"120\">\n <vf-icon label=\"System\" width=\"64\" top=\"8\" left=\"12\">…</vf-icon>\n <vf-icon label=\"Finder\" width=\"64\" top=\"8\" left=\"104\">…</vf-icon>\n</vf-container>\n```\n\nThe rectangle is the whole API — `width`/`height` here, plus the `top`/`left`\npair nearly every component takes (VfPositioned), so a container is\nitself placeable: inside a window, a desktop, or another container, at whole\nsystem px that keep its box on the device-pixel grid by construction.\n\n**It is not a `vf-stack`.** The stack is a flexbox with opinions — it\ndistributes children along an axis, compiles `fill-width`/`fill-height` into\nflex, defaults a cross-axis alignment. This box has no layout opinion at\nall: in-flow children get normal flow, placed children get a coordinate\nsystem, and that is the whole API. Reach for it when the stack's opinions\nare the thing in the way — a field of placed icons, a fixed stage for\nabsolutely-positioned art, a consumer's own composition that brings its\nlayout with it.\n\n**The declared size is the layout.** `width`/`height` land on the host as a\nlive `calc(var(--vf-scale, 1) * Npx)` (VfSized), so the box scales\nwith the display and sits on the device-pixel grid by construction. Content\nthat outgrows the box overflows it rather than growing it — the number is\nthe layout, and content that doesn't fit is a number to raise. Leave a\ndimension off and that axis shrink-wraps: `fit-content`, not the parent's\nwidth, because a layout box that silently claimed a size nobody declared\nwould be inventing one (the `vf-stack` rule, held here too).\n\n**It paints nothing and means nothing.** No border, background, role,\nkeyboard behavior or selection — what it holds decides what it is.\n\n**It holds its box on the device-pixel grid** — with a `GridSnapController`.\nA container's box is itself the consumer's coordinate system, including for\nnon-`vf` content that cannot correct itself, so the box is the thing to\nhold on the grid. The shadow box below owns the `position: relative` anchor\nand the `vf-snap` class together, so under `applyGridSnap()` the correction\nmoves the whole coordinate system — everything placed against it rides\nalong instead of being re-corrected child by child. (`vf-stack` shipped\nwithout a controller on the theory that slotted `vf-*` children correct\ntheir own origins; this component is where that theory's gap — consumer\ncontent — became visible, and the stack has since adopted the same\narrangement.)\n\nLike the stack it is **typographically transparent**: `vfBase`'s chrome\ndress is returned to `inherit` on the host, so wrapping content in a sized\nbox changes nothing about how that content reads.\n\n`fill-width` / `fill-height` work here the way they do everywhere: read\nabout the host (be as big as *its* parent allows), and compiled for slotted\nchildren — `width: 100%` in normal flow, so a child filling the cross of a\ndeclared box needs no stylesheet. A height fill needs a declared `height`\nto resolve against; with none it is inert, not an error.",
1977
+ "description": "`<vf-container>` — a box that is nothing but its declared size.\n\nThe kit's positioned-placement story (src/position.ts) ends with one line of\nCSS it can't write for you: children placed with `top`/`left` need a\npositioned ancestor, and while every kit container is one — a window body, a\nstack, a fieldset — a region of *your own* needs `position: relative` in a\nstylesheet. This component is that region as an element: declare `width` and\n`height` in whole system px, slot anything into it, place children against\nits origin. A DITL's enclosing rectangle, with nothing drawn in it.\n\n```html\n<vf-container width=\"200\" height=\"120\">\n <vf-icon label=\"System\" width=\"64\" top=\"8\" left=\"12\">…</vf-icon>\n <vf-icon label=\"Finder\" width=\"64\" top=\"8\" left=\"104\">…</vf-icon>\n</vf-container>\n```\n\nThe rectangle is the whole API — `width`/`height` here, plus the `top`/`left`\npair nearly every component takes (VfPositioned), so a container is\nitself placeable: inside a window, a desktop, or another container, at whole\nsystem px that keep its box on the device-pixel grid by construction.\n\n**It is not a `vf-stack`.** The stack is a flexbox with opinions — it\ndistributes children along an axis, compiles `fill-width`/`fill-height` into\nflex, defaults a cross-axis alignment. This box has no layout opinion at\nall: in-flow children get normal flow, placed children get a coordinate\nsystem, and that is the whole API. Reach for it when the stack's opinions\nare the thing in the way — a field of placed icons, a fixed stage for\nabsolutely-positioned art, a consumer's own composition that brings its\nlayout with it.\n\n**The declared size is the layout.** `width`/`height` land on the host as a\nlive `calc(var(--vf-scale, 1) * Npx)` (VfSized), so the box scales\nwith the display and sits on the device-pixel grid by construction. Content\nthat outgrows the box overflows it rather than growing it — the number is\nthe layout, and content that doesn't fit is a number to raise. Leave a\ndimension off and that axis shrink-wraps: `fit-content`, not the parent's\nwidth, because a layout box that silently claimed a size nobody declared\nwould be inventing one (the `vf-stack` rule, held here too).\n\n**It paints nothing and means nothing.** No border, background, role,\nkeyboard behavior or selection — what it holds decides what it is.\n\n**It holds its box on the device-pixel grid** — with a `GridSnapController`.\nA container's box is itself the consumer's coordinate system, including for\nnon-`vf` content that cannot correct itself, so the box is the thing to\nhold on the grid. The shadow box below owns the `position: relative` anchor\nand the `vf-snap` class together, so the correction\nmoves the whole coordinate system — everything placed against it rides\nalong instead of being re-corrected child by child. (`vf-stack` shipped\nwithout a controller on the theory that slotted `vf-*` children correct\ntheir own origins; this component is where that theory's gap — consumer\ncontent — became visible, and the stack has since adopted the same\narrangement.)\n\nLike the stack it is **typographically transparent**: `vfBase`'s chrome\ndress is returned to `inherit` on the host, so wrapping content in a sized\nbox changes nothing about how that content reads.\n\n`fill-width` / `fill-height` work here the way they do everywhere: read\nabout the host (be as big as *its* parent allows), and compiled for slotted\nchildren — `width: 100%` in normal flow, so a child filling the cross of a\ndeclared box needs no stylesheet. A height fill needs a declared `height`\nto resolve against; with none it is inert, not an error.",
1950
1978
  "name": "VfContainer",
1951
1979
  "slots": [
1952
1980
  {
@@ -1969,7 +1997,7 @@
1969
1997
  "privacy": "private",
1970
1998
  "readonly": true,
1971
1999
  "default": "new GridSnapController(this)",
1972
- "description": "Hold the box on the device-pixel grid under `applyGridSnap()` — see the\nclass doc. The host is what gets measured; `.box` (vf-snap) is where the\ncorrection lands."
2000
+ "description": "Hold the box on the device-pixel grid — see the\nclass doc. The host is what gets measured; `.box` (vf-snap) is where the\ncorrection lands."
1973
2001
  },
1974
2002
  {
1975
2003
  "kind": "field",
@@ -2220,7 +2248,7 @@
2220
2248
  "privacy": "private",
2221
2249
  "readonly": true,
2222
2250
  "default": "new GridSnapController(this)",
2223
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
2251
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
2224
2252
  },
2225
2253
  {
2226
2254
  "kind": "field",
@@ -3445,7 +3473,7 @@
3445
3473
  "privacy": "private",
3446
3474
  "readonly": true,
3447
3475
  "default": "new GridSnapController(this)",
3448
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
3476
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
3449
3477
  },
3450
3478
  {
3451
3479
  "kind": "field",
@@ -3697,7 +3725,7 @@
3697
3725
  "privacy": "private",
3698
3726
  "readonly": true,
3699
3727
  "default": "new GridSnapController(this)",
3700
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
3728
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
3701
3729
  },
3702
3730
  {
3703
3731
  "kind": "field",
@@ -4007,7 +4035,7 @@
4007
4035
  "privacy": "private",
4008
4036
  "readonly": true,
4009
4037
  "default": "new GridSnapController(this)",
4010
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
4038
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
4011
4039
  },
4012
4040
  {
4013
4041
  "kind": "field",
@@ -4045,7 +4073,7 @@
4045
4073
  "text": "number"
4046
4074
  },
4047
4075
  "default": "0",
4048
- "description": "The plate's width in whole, EVEN system px — 0 until the text is measured.\nSee #measurePlate; this is what keeps every name on the grid.\n\nDeliberately NOT `@state()`. The width can only be known by rendering the\nname and measuring it, so a reactive field would make every icon render\ntwice — once to measure, once to deliver one number back — and Lit's\nchange-in-update warning is exactly that round trip being spotted. It\nfeeds a single inline width and nothing else, so #measurePlate\nwrites it straight to the plate instead, the ownership `applyGridSnap()`\nhas over `--vf-snap-dx`/`-dy`. The template still reads it so a caption\nLit re-creates (one leaving `_editing`, say) is born at the settled width\nrather than flashing through the unmeasured one."
4076
+ "description": "The plate's width in whole, EVEN system px — 0 until the text is measured.\nSee #measurePlate; this is what keeps every name on the grid.\n\nDeliberately NOT `@state()`. The width can only be known by rendering the\nname and measuring it, so a reactive field would make every icon render\ntwice — once to measure, once to deliver one number back — and Lit's\nchange-in-update warning is exactly that round trip being spotted. It\nfeeds a single inline width and nothing else, so #measurePlate\nwrites it straight to the plate instead, the ownership the grid snapper\nhas over `--vf-snap-dx`/`-dy`. The template still reads it so a caption\nLit re-creates (one leaving `_editing`, say) is born at the settled width\nrather than flashing through the unmeasured one."
4049
4077
  },
4050
4078
  {
4051
4079
  "kind": "field",
@@ -4626,7 +4654,7 @@
4626
4654
  "privacy": "private",
4627
4655
  "readonly": true,
4628
4656
  "default": "new GridSnapController(this)",
4629
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
4657
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
4630
4658
  },
4631
4659
  {
4632
4660
  "kind": "field",
@@ -4857,7 +4885,7 @@
4857
4885
  "privacy": "private",
4858
4886
  "readonly": true,
4859
4887
  "default": "new GridSnapController(this)",
4860
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
4888
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
4861
4889
  },
4862
4890
  {
4863
4891
  "kind": "field",
@@ -5134,7 +5162,7 @@
5134
5162
  "name": "VfListItem",
5135
5163
  "cssParts": [
5136
5164
  {
5137
- "description": "The text span beside the icon gutter (ellipsizes).",
5165
+ "description": "The text span beside the icon gutter (ellipsizes). Takes `top`/`left` like every other element ({@link VfPositioned}), for the consumer who wants a row somewhere other than a list. Inside its parent `<vf-list>` the rows stack in flow, so stating an origin lifts that row out of the stack and the ones below it close the gap — the placement doing what it says, but not how a list box is laid out.",
5138
5166
  "name": "text"
5139
5167
  }
5140
5168
  ],
@@ -5211,6 +5239,32 @@
5211
5239
  "text": "string"
5212
5240
  },
5213
5241
  "default": "'option'"
5242
+ },
5243
+ {
5244
+ "kind": "field",
5245
+ "name": "top",
5246
+ "type": {
5247
+ "text": "number | null | undefined"
5248
+ },
5249
+ "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
5250
+ "attribute": "top",
5251
+ "inheritedFrom": {
5252
+ "name": "VfPositioned",
5253
+ "module": "src/position.ts"
5254
+ }
5255
+ },
5256
+ {
5257
+ "kind": "field",
5258
+ "name": "left",
5259
+ "type": {
5260
+ "text": "number | null | undefined"
5261
+ },
5262
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
5263
+ "attribute": "left",
5264
+ "inheritedFrom": {
5265
+ "name": "VfPositioned",
5266
+ "module": "src/position.ts"
5267
+ }
5214
5268
  }
5215
5269
  ],
5216
5270
  "attributes": [
@@ -5240,6 +5294,36 @@
5240
5294
  "default": "false",
5241
5295
  "description": "Disables the row: dimmed text, not selectable or focusable.",
5242
5296
  "fieldName": "disabled"
5297
+ },
5298
+ {
5299
+ "name": "top",
5300
+ "type": {
5301
+ "text": "number | null | undefined"
5302
+ },
5303
+ "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
5304
+ "fieldName": "top",
5305
+ "inheritedFrom": {
5306
+ "name": "VfPositioned",
5307
+ "module": "src/position.ts"
5308
+ }
5309
+ },
5310
+ {
5311
+ "name": "left",
5312
+ "type": {
5313
+ "text": "number | null | undefined"
5314
+ },
5315
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
5316
+ "fieldName": "left",
5317
+ "inheritedFrom": {
5318
+ "name": "VfPositioned",
5319
+ "module": "src/position.ts"
5320
+ }
5321
+ }
5322
+ ],
5323
+ "mixins": [
5324
+ {
5325
+ "name": "VfPositioned",
5326
+ "module": "/src/position.js"
5243
5327
  }
5244
5328
  ],
5245
5329
  "superclass": {
@@ -5318,7 +5402,7 @@
5318
5402
  "privacy": "private",
5319
5403
  "readonly": true,
5320
5404
  "default": "new GridSnapController(this)",
5321
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
5405
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
5322
5406
  },
5323
5407
  {
5324
5408
  "kind": "field",
@@ -5844,7 +5928,7 @@
5844
5928
  "privacy": "private",
5845
5929
  "readonly": true,
5846
5930
  "default": "new GridSnapController(this)",
5847
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
5931
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
5848
5932
  },
5849
5933
  {
5850
5934
  "kind": "field",
@@ -6198,7 +6282,7 @@
6198
6282
  "declarations": [
6199
6283
  {
6200
6284
  "kind": "class",
6201
- "description": "`<vf-menu-item>` — a single command inside a `<vf-menu>` panel.\n\nRenders the classic System 7 menu row: optional ✓ check in the\n`--vf-select-gutter` left column (16px, shared with vf-select/vf-option),\nlabel, and the keyboard shortcut left-aligned in a right-anchored column,\nso every ⌘ lands at the same x. On activation the item performs the classic\n3-blink inversion (~250ms), then dispatches `vf-menu-select` and asks its\nancestors to close the menu.",
6285
+ "description": "`<vf-menu-item>` — a single command inside a `<vf-menu>` panel.\n\nRenders the classic System 7 menu row: optional ✓ check in the\n`--vf-select-gutter` left column (16px, shared with vf-select/vf-option),\nlabel, and the keyboard shortcut left-aligned in a right-anchored column,\nso every ⌘ lands at the same x. On activation the item performs the classic\n3-blink inversion (~250ms), then dispatches `vf-menu-select` and asks its\nancestors to close the menu.\n\nTakes `top`/`left` like every other element (VfPositioned), for the\nconsumer who wants a row somewhere other than a pulldown. Inside its parent\n`<vf-menu>` the panel is as wide as its widest row and stacks them in flow,\nso stating an origin takes that row out of both: it no longer contributes to\nthe panel's width and the rows below close the gap. The placement doing what\nit says — but not how a pulldown is laid out.",
6202
6286
  "name": "VfMenuItem",
6203
6287
  "cssProperties": [
6204
6288
  {
@@ -6475,6 +6559,32 @@
6475
6559
  "text": "void"
6476
6560
  }
6477
6561
  }
6562
+ },
6563
+ {
6564
+ "kind": "field",
6565
+ "name": "top",
6566
+ "type": {
6567
+ "text": "number | null | undefined"
6568
+ },
6569
+ "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
6570
+ "attribute": "top",
6571
+ "inheritedFrom": {
6572
+ "name": "VfPositioned",
6573
+ "module": "src/position.ts"
6574
+ }
6575
+ },
6576
+ {
6577
+ "kind": "field",
6578
+ "name": "left",
6579
+ "type": {
6580
+ "text": "number | null | undefined"
6581
+ },
6582
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
6583
+ "attribute": "left",
6584
+ "inheritedFrom": {
6585
+ "name": "VfPositioned",
6586
+ "module": "src/position.ts"
6587
+ }
6478
6588
  }
6479
6589
  ],
6480
6590
  "events": [
@@ -6536,6 +6646,36 @@
6536
6646
  },
6537
6647
  "description": "Value reported in the `vf-menu-select` event detail. Defaults to the item's\ntrimmed text content when unset.",
6538
6648
  "fieldName": "value"
6649
+ },
6650
+ {
6651
+ "name": "top",
6652
+ "type": {
6653
+ "text": "number | null | undefined"
6654
+ },
6655
+ "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
6656
+ "fieldName": "top",
6657
+ "inheritedFrom": {
6658
+ "name": "VfPositioned",
6659
+ "module": "src/position.ts"
6660
+ }
6661
+ },
6662
+ {
6663
+ "name": "left",
6664
+ "type": {
6665
+ "text": "number | null | undefined"
6666
+ },
6667
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
6668
+ "fieldName": "left",
6669
+ "inheritedFrom": {
6670
+ "name": "VfPositioned",
6671
+ "module": "src/position.ts"
6672
+ }
6673
+ }
6674
+ ],
6675
+ "mixins": [
6676
+ {
6677
+ "name": "VfPositioned",
6678
+ "module": "/src/position.js"
6539
6679
  }
6540
6680
  ],
6541
6681
  "superclass": {
@@ -6571,7 +6711,7 @@
6571
6711
  "declarations": [
6572
6712
  {
6573
6713
  "kind": "class",
6574
- "description": "`<vf-menu>` — a pull-down menu: a bar label plus a dropped panel of\n`<vf-menu-item>` / `<vf-separator>` children.\n\nInside a `<vf-menu-bar>` the bar coordinates open state (only one menu open,\nhover-switching, outside-click/Escape dismissal) and owns the pointer\ngesture, which may travel between its menus. Used standalone, the menu\ntoggles itself on label click and manages its own dismissal, its own press\ngesture and item keyboard navigation (ArrowUp/ArrowDown, Home/End, and the\nshared first-letter type-ahead — src/type-ahead.ts) while open.\n\nPointer — the two styles `vf-select` supports, on the same terms (see\nsrc/menu-press.ts): the System 7 press-drag-release (press the title, slide\nonto a command, release over it) and a modern quick tap that leaves the menu\ndropped for a second click.",
6714
+ "description": "`<vf-menu>` — a pull-down menu: a bar label plus a dropped panel of\n`<vf-menu-item>` / `<vf-separator>` children.\n\nInside a `<vf-menu-bar>` the bar coordinates open state (only one menu open,\nhover-switching, outside-click/Escape dismissal) and owns the pointer\ngesture, which may travel between its menus. Used standalone, the menu\ntoggles itself on label click and manages its own dismissal, its own press\ngesture and item keyboard navigation (ArrowUp/ArrowDown, Home/End, and the\nshared first-letter type-ahead — src/type-ahead.ts) while open.\n\nPointer — the two styles `vf-select` supports, on the same terms (see\nsrc/menu-press.ts): the System 7 press-drag-release (press the title, slide\nonto a command, release over it) and a modern quick tap that leaves the menu\ndropped for a second click.\n\nTakes `top`/`left` like every other element (VfPositioned) — the\nnatural fit being a standalone menu, which is a free-standing menu button and\nplaces like any other control. The panel is anchored to the host's own box\n(`top: 100%`), so it follows a placed menu down without any further work.\nInside a `<vf-menu-bar>`, the bar lays its titles out in flow — placing one\nlifts it off the bar and the titles beside it close the gap.",
6575
6715
  "name": "VfMenu",
6576
6716
  "cssProperties": [
6577
6717
  {
@@ -6619,7 +6759,7 @@
6619
6759
  "privacy": "private",
6620
6760
  "readonly": true,
6621
6761
  "default": "new GridSnapController(this)",
6622
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
6762
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
6623
6763
  },
6624
6764
  {
6625
6765
  "kind": "field",
@@ -6918,6 +7058,32 @@
6918
7058
  }
6919
7059
  },
6920
7060
  "description": "Focuses the first enabled item once the panel is visible. The reflected\n`open` attribute (which un-hides the panel) only lands on the host in the\nnext Lit update, so focusing synchronously would silently no-op."
7061
+ },
7062
+ {
7063
+ "kind": "field",
7064
+ "name": "top",
7065
+ "type": {
7066
+ "text": "number | null | undefined"
7067
+ },
7068
+ "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
7069
+ "attribute": "top",
7070
+ "inheritedFrom": {
7071
+ "name": "VfPositioned",
7072
+ "module": "src/position.ts"
7073
+ }
7074
+ },
7075
+ {
7076
+ "kind": "field",
7077
+ "name": "left",
7078
+ "type": {
7079
+ "text": "number | null | undefined"
7080
+ },
7081
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
7082
+ "attribute": "left",
7083
+ "inheritedFrom": {
7084
+ "name": "VfPositioned",
7085
+ "module": "src/position.ts"
7086
+ }
6921
7087
  }
6922
7088
  ],
6923
7089
  "attributes": [
@@ -6938,6 +7104,36 @@
6938
7104
  "default": "false",
6939
7105
  "description": "Whether the panel is dropped. Reflected. Managed by the parent\n`vf-menu-bar` when present, otherwise by the menu itself.",
6940
7106
  "fieldName": "open"
7107
+ },
7108
+ {
7109
+ "name": "top",
7110
+ "type": {
7111
+ "text": "number | null | undefined"
7112
+ },
7113
+ "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
7114
+ "fieldName": "top",
7115
+ "inheritedFrom": {
7116
+ "name": "VfPositioned",
7117
+ "module": "src/position.ts"
7118
+ }
7119
+ },
7120
+ {
7121
+ "name": "left",
7122
+ "type": {
7123
+ "text": "number | null | undefined"
7124
+ },
7125
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
7126
+ "fieldName": "left",
7127
+ "inheritedFrom": {
7128
+ "name": "VfPositioned",
7129
+ "module": "src/position.ts"
7130
+ }
7131
+ }
7132
+ ],
7133
+ "mixins": [
7134
+ {
7135
+ "name": "VfPositioned",
7136
+ "module": "/src/position.js"
6941
7137
  }
6942
7138
  ],
6943
7139
  "superclass": {
@@ -7384,7 +7580,7 @@
7384
7580
  "privacy": "protected",
7385
7581
  "readonly": true,
7386
7582
  "default": "new GridSnapController(this)",
7387
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts.",
7583
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts.",
7388
7584
  "inheritedFrom": {
7389
7585
  "name": "VfTextControlBase",
7390
7586
  "module": "src/text-control.ts"
@@ -8283,7 +8479,7 @@
8283
8479
  "declarations": [
8284
8480
  {
8285
8481
  "kind": "class",
8286
- "description": "`<vf-option>` — a single choice inside a `<vf-select>` popup menu.\n\nA light-DOM child of `<vf-select>` (slotted into the popup panel). Renders\nits slotted label at menu-item metrics (16px row — the pill's content height,\nso a selected option overlays the closed pill exactly; the left checkmark gutter is\n`--vf-select-gutter`, shared with the closed control's left inset so the value\ndoesn't shift on open). The parent select manages `selected` and the transient\n`active` highlight, and slots this element into its popup panel.\n\nThe host carries `role=\"option\"` with `aria-selected`/`aria-disabled`.",
8482
+ "description": "`<vf-option>` — a single choice inside a `<vf-select>` popup menu.\n\nA light-DOM child of `<vf-select>` (slotted into the popup panel). Renders\nits slotted label at menu-item metrics (16px row — the pill's content height,\nso a selected option overlays the closed pill exactly; the left checkmark gutter is\n`--vf-select-gutter`, shared with the closed control's left inset so the value\ndoesn't shift on open). The parent select manages `selected` and the transient\n`active` highlight, and slots this element into its popup panel.\n\nThe host carries `role=\"option\"` with `aria-selected`/`aria-disabled`.\n\nTakes `top`/`left` like every other element (VfPositioned), for the\nconsumer who wants one somewhere other than a popup row. Inside its parent\n`<vf-select>` it is a row the panel measures and scrolls in flow, so stating\nan origin there takes it out of that measurement: the panel stops counting it\ntoward its own width and its scroll clamp, and the rows below close the gap.\nThat is the placement doing exactly what it says — not a bug — but it is not\nhow a popup is laid out.",
8287
8483
  "name": "VfOption",
8288
8484
  "cssProperties": [
8289
8485
  {
@@ -8368,6 +8564,32 @@
8368
8564
  "text": "string"
8369
8565
  },
8370
8566
  "default": "'option'"
8567
+ },
8568
+ {
8569
+ "kind": "field",
8570
+ "name": "top",
8571
+ "type": {
8572
+ "text": "number | null | undefined"
8573
+ },
8574
+ "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
8575
+ "attribute": "top",
8576
+ "inheritedFrom": {
8577
+ "name": "VfPositioned",
8578
+ "module": "src/position.ts"
8579
+ }
8580
+ },
8581
+ {
8582
+ "kind": "field",
8583
+ "name": "left",
8584
+ "type": {
8585
+ "text": "number | null | undefined"
8586
+ },
8587
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
8588
+ "attribute": "left",
8589
+ "inheritedFrom": {
8590
+ "name": "VfPositioned",
8591
+ "module": "src/position.ts"
8592
+ }
8371
8593
  }
8372
8594
  ],
8373
8595
  "attributes": [
@@ -8406,6 +8628,36 @@
8406
8628
  "default": "false",
8407
8629
  "description": "Transient highlight (hover / keyboard cursor) — full-row inversion.\nManaged by the parent `<vf-select>`; not part of the authoring API.",
8408
8630
  "fieldName": "active"
8631
+ },
8632
+ {
8633
+ "name": "top",
8634
+ "type": {
8635
+ "text": "number | null | undefined"
8636
+ },
8637
+ "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
8638
+ "fieldName": "top",
8639
+ "inheritedFrom": {
8640
+ "name": "VfPositioned",
8641
+ "module": "src/position.ts"
8642
+ }
8643
+ },
8644
+ {
8645
+ "name": "left",
8646
+ "type": {
8647
+ "text": "number | null | undefined"
8648
+ },
8649
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
8650
+ "fieldName": "left",
8651
+ "inheritedFrom": {
8652
+ "name": "VfPositioned",
8653
+ "module": "src/position.ts"
8654
+ }
8655
+ }
8656
+ ],
8657
+ "mixins": [
8658
+ {
8659
+ "name": "VfPositioned",
8660
+ "module": "/src/position.js"
8409
8661
  }
8410
8662
  ],
8411
8663
  "superclass": {
@@ -8441,7 +8693,7 @@
8441
8693
  "declarations": [
8442
8694
  {
8443
8695
  "kind": "class",
8444
- "description": "`<vf-paragraph>` — a paragraph of copy on the kit's body face and grid.\n\nThe installer's welcome text, a dialog's explanation, an article's prose:\nanything a page would otherwise set with a `<p>` and hope the leading landed\nsomewhere sensible. What it adds over that `<p>`:\n\n- the **Geneva body face** by default (`face=\"display\"` switches to\n the Chicago-style chrome face);\n- a **whole-system-pixel line box at the face's native pitch** —\n `--vf-paragraph-line-height`: 12px for the body face (Geneva 9's own\n strike line) and 16px under `face=\"display\"` (Chicago 12's — ascent 12 +\n descent 3 + leading 1, the pitch a real dialog wrapped its copy on).\n Whole pixels are the point of the component: line boxes are the single\n biggest source of off-grid layout, because a ratio resolves to whatever\n it resolves to (`1.65 × 17px = 28.05px`) and every line of prose nudges\n everything after it further off the device-pixel grid, smearing 1-bit\n borders and bitmap glyph stems (docs/SIZING.md rule 2). A\n whole-pixel line box accumulates whole offsets;\n- its own GridSnapController, so it holds its own origin once the\n page opts in with `applyGridSnap()`;\n- a **declared box** when the layout wants one — `width`/`height` in whole\n system px (VfSized). In flow a paragraph takes its container's\n width, which is usually right; a *placed* one (`top`/`left`) shrink-wraps\n its longest line instead — a fractional glyph-run width, wrapped wherever\n the parent's edge happens to fall — so a DITL-style layout states the\n measure the copy wraps to, whole and on the grid.\n\nThe shadow root renders a real `<p>`, so the copy keeps paragraph semantics\nfor assistive tech, and — unlike the kit's chrome — the text is selectable.\nThere is no margin: per SPEC §2 a component adds nothing outside its own box,\nso paragraph spacing is the page's (a `gap` on the column, or a margin on the\nhost — kept a whole number of pixels, like everything else in the contract).",
8696
+ "description": "`<vf-paragraph>` — a paragraph of copy on the kit's body face and grid.\n\nThe installer's welcome text, a dialog's explanation, an article's prose:\nanything a page would otherwise set with a `<p>` and hope the leading landed\nsomewhere sensible. What it adds over that `<p>`:\n\n- the **Geneva body face** by default (`face=\"display\"` switches to\n the Chicago-style chrome face);\n- a **whole-system-pixel line box at the face's native pitch** —\n `--vf-paragraph-line-height`: 12px for the body face (Geneva 9's own\n strike line) and 16px under `face=\"display\"` (Chicago 12's — ascent 12 +\n descent 3 + leading 1, the pitch a real dialog wrapped its copy on).\n Whole pixels are the point of the component: line boxes are the single\n biggest source of off-grid layout, because a ratio resolves to whatever\n it resolves to (`1.65 × 17px = 28.05px`) and every line of prose nudges\n everything after it further off the device-pixel grid, smearing 1-bit\n borders and bitmap glyph stems (docs/SIZING.md rule 2). A\n whole-pixel line box accumulates whole offsets;\n- its own GridSnapController, so it holds its own origin\n wherever the page puts it;\n- a **declared box** when the layout wants one — `width`/`height` in whole\n system px (VfSized). In flow a paragraph takes its container's\n width, which is usually right; a *placed* one (`top`/`left`) shrink-wraps\n its longest line instead — a fractional glyph-run width, wrapped wherever\n the parent's edge happens to fall — so a DITL-style layout states the\n measure the copy wraps to, whole and on the grid.\n\nThe shadow root renders a real `<p>`, so the copy keeps paragraph semantics\nfor assistive tech, and — unlike the kit's chrome — the text is selectable.\nThere is no margin: per SPEC §2 a component adds nothing outside its own box,\nso paragraph spacing is the page's (a `gap` on the column, or a margin on the\nhost — kept a whole number of pixels, like everything else in the contract).",
8445
8697
  "name": "VfParagraph",
8446
8698
  "cssProperties": [
8447
8699
  {
@@ -8484,7 +8736,7 @@
8484
8736
  "privacy": "private",
8485
8737
  "readonly": true,
8486
8738
  "default": "new GridSnapController(this)",
8487
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
8739
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
8488
8740
  },
8489
8741
  {
8490
8742
  "kind": "field",
@@ -8712,7 +8964,7 @@
8712
8964
  "privacy": "private",
8713
8965
  "readonly": true,
8714
8966
  "default": "new GridSnapController(this)",
8715
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
8967
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
8716
8968
  },
8717
8969
  {
8718
8970
  "kind": "field",
@@ -9742,7 +9994,7 @@
9742
9994
  ],
9743
9995
  "slots": [
9744
9996
  {
9745
- "description": "The label, rendered to the right of the circle with a 6px gap.",
9997
+ "description": "The label, rendered to the right of the circle with a 6px gap. Left empty, the label and its gap collapse: the control is the bare 13×13 circle.",
9746
9998
  "name": ""
9747
9999
  }
9748
10000
  ],
@@ -9792,6 +10044,16 @@
9792
10044
  "default": "false",
9793
10045
  "description": "True while the containing `vf-radio-group` is disabled. Managed by the\ngroup — not intended to be set by consumers."
9794
10046
  },
10047
+ {
10048
+ "kind": "field",
10049
+ "name": "_hasLabel",
10050
+ "type": {
10051
+ "text": "boolean"
10052
+ },
10053
+ "privacy": "private",
10054
+ "default": "false",
10055
+ "description": "Whether the slot holds non-whitespace content; gates the label row."
10056
+ },
9795
10057
  {
9796
10058
  "kind": "field",
9797
10059
  "name": "internals",
@@ -9843,6 +10105,24 @@
9843
10105
  "module": "src/toggle-control.ts"
9844
10106
  }
9845
10107
  },
10108
+ {
10109
+ "kind": "method",
10110
+ "name": "#onLabelSlotChange",
10111
+ "privacy": "private",
10112
+ "return": {
10113
+ "type": {
10114
+ "text": "void"
10115
+ }
10116
+ },
10117
+ "parameters": [
10118
+ {
10119
+ "name": "event",
10120
+ "type": {
10121
+ "text": "Event"
10122
+ }
10123
+ }
10124
+ ]
10125
+ },
9846
10126
  {
9847
10127
  "kind": "method",
9848
10128
  "name": "activate",
@@ -9910,7 +10190,7 @@
9910
10190
  "privacy": "private",
9911
10191
  "readonly": true,
9912
10192
  "default": "new GridSnapController(this)",
9913
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts.",
10193
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts.",
9914
10194
  "inheritedFrom": {
9915
10195
  "name": "VfToggleControl",
9916
10196
  "module": "src/toggle-control.ts"
@@ -10134,7 +10414,7 @@
10134
10414
  "privacy": "private",
10135
10415
  "readonly": true,
10136
10416
  "default": "new GridSnapController(this)",
10137
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
10417
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
10138
10418
  },
10139
10419
  {
10140
10420
  "kind": "field",
@@ -10534,7 +10814,7 @@
10534
10814
  "privacy": "private",
10535
10815
  "readonly": true,
10536
10816
  "default": "new GridSnapController(this)",
10537
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
10817
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
10538
10818
  },
10539
10819
  {
10540
10820
  "kind": "field",
@@ -12006,7 +12286,7 @@
12006
12286
  "privacy": "private",
12007
12287
  "readonly": true,
12008
12288
  "default": "new GridSnapController(this)",
12009
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
12289
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
12010
12290
  },
12011
12291
  {
12012
12292
  "kind": "field",
@@ -12243,7 +12523,7 @@
12243
12523
  "privacy": "private",
12244
12524
  "readonly": true,
12245
12525
  "default": "new GridSnapController(this)",
12246
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
12526
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
12247
12527
  },
12248
12528
  {
12249
12529
  "kind": "field",
@@ -13074,7 +13354,7 @@
13074
13354
  "declarations": [
13075
13355
  {
13076
13356
  "kind": "class",
13077
- "description": "`<vf-stack>` — arrange things inside a window, in system pixels.\n\nA window body is the one place the kit stopped short: every control inside it\nis authored in system px, but the *spaces between* them were the consumer's\nproblem, written by hand as `calc(var(--vf-scale, 1) * 12px)`. This is that\ncalculation, as a component — a flexbox whose `gap`, `pad`, `width` and\n`height` are declared in whole system px and converted internally:\n\n```html\n<vf-stack gap=\"12\"> <!-- a column -->\n <vf-stack fill-width direction=\"row\" gap=\"8\"> <!-- a labeled field -->\n <vf-label width=\"80\" for=\"name\">Name:</vf-label>\n <vf-text-field id=\"name\" fill-width></vf-text-field>\n </vf-stack>\n <vf-stack fill-width place=\"end\"> <!-- an action row -->\n <vf-button-group>\n <vf-button>Cancel</vf-button>\n <vf-button variant=\"default\">Save</vf-button>\n </vf-button-group>\n </vf-stack>\n</vf-stack>\n```\n\n**Why this can't just be page CSS.** Scaling is default-on and *per\ncomponent*: `ScaleController` sets `--vf-scale` on the component's own host,\nnot on the document. So `var(--vf-scale, 1)` in a consumer's stylesheet\nresolves only where the rule's element happens to sit inside a `vf-*`\nancestor and inherit it — true inside a window body, false for a plain\n`<div>` holding two buttons on an ordinary page, where the fallback `1` wins\nand the gap renders at 8px around 3×-sized buttons with no warning. A page\nthat hasn't called `applyScale()` has no way to write \"8 system px\" at all.\nA component always can, because it *is* the scope — which is also why this\none carries a ScaleController of its own: without it a lone stack\nwould resolve its own gap against that same fallback while its children each\nself-scaled around it.\n\n**Whole system px is the only expressible value**, so the gap half of the\nlayout contract (docs/SIZING.md rule 2) stops being a rule to remember. Declaring\n`width`/`height` covers the size half of rule 3 as well.\n\n**The geometry is governed by the content.** A column is as wide as its\nwidest child and a row as tall as its tallest; children keep the size they\ndrew themselves at (`flex: 0 0 auto` — no growing, no shrinking). System 7\nboxes are the size they are: a push button is as wide as its label, a popup\nmenu hugs its widest option, and a window is a fixed box whose overflow is\nclipped at the frame, not a layout that squeezes its controls to fit. The\nstack distributes; it does not resize. That is why the box shrink-wraps\n(`fit-content`) rather than claiming its parent's whole width — a layout box\nthat did would be inventing a size nobody declared.\n\n**`fill-width` / `fill-height` are how a child asks for more**, as bare\nattributes on consumer DOM the way `nosnap` opts an element out of snapping:\n\n```html\n<vf-text-field fill-width></vf-text-field>\n```\n\nEach names the *outcome*, not the axis, so the markup means the same thing\nwherever it lands; the stack does the flexbox translation, which is the whole\nreason to have a component. One rule to learn, about geometry rather than\nvocabulary: **the cross axis always has a size, the main axis only has slack\nif you declared one.** So `fill-width` always works in a column (the width is\nthe widest child's) and needs a declared `width` in a row; `fill-height` is\nexactly the other way round. A fill with nothing to take is inert, not an\nerror. Two children filling along the main axis end up *equal* — the zeroed\nflex basis is what lets them divide the slack rather than keep their natural\nsizes — and a child that declares its own size shouldn't also ask to fill it.\n\nA stack reads the same two attributes about *itself*, for the parents that\naren't stacks: a window body, a fieldset, a scroll well, a grid cell. That is\nwhere a panel's width enters the tree, and from there `fill-width` hands it\ndown a level at a time.\n\n**It paints nothing and means nothing.** No border, no background, no role,\nno keyboard behavior — what it holds decides what it is, as with\nVfGrid.\n\n**It holds its box on the device-pixel grid** — the `vf-container`\narrangement, adopted here after first shipping without it. The original\nreasoning (\"no ink of its own, and slotted `vf-*` children correct their own\norigins\") accounted only for kit children: a stack is a positioned ancestor\nand a layout box for *anything*, and consumer content inside it — a div, an\n`<img>`, a run of text — has no controller of its own. So the flex\ncontainer, the placed-child anchor and the `vf-snap` class live on one\nshadow box, and under `applyGridSnap()` the correction moves the stack's\nwhole coordinate system; children (kit or not) ride it, and the sweep's\noutermost-first order means the kit children then find nothing left to\ncorrect. What this deliberately does not fix: a text-governed child width\nmid-row still shifts later siblings fractionally (their own controllers\ncover that), and centering still can't land on a whole pixel by itself.\n\nIt is also the kit's one **typographically transparent** component: `vfBase`'s\nchrome face, ratio line box, color and non-selectability are all reset to\n`inherit` on the host. Wrapping content in a layout box must not change how\nthat content reads — inside a window it goes on inheriting the window's face\nexactly as before, and on a plain page it leaves the page's typography (and\nits whole-pixel line boxes) alone.\n\nWhat it deliberately does **not** do: equalize its children's widths. A row of\nbuttons still belongs in a `vf-button-group`, which sizes them all to the\nwidest and aligns their *faces* rather than the `variant=\"default\"` ring\nboxes a plain flex row would line up.",
13357
+ "description": "`<vf-stack>` — arrange things inside a window, in system pixels.\n\nA window body is the one place the kit stopped short: every control inside it\nis authored in system px, but the *spaces between* them were the consumer's\nproblem, written by hand as `calc(var(--vf-scale, 1) * 12px)`. This is that\ncalculation, as a component — a flexbox whose `gap`, `pad`, `width` and\n`height` are declared in whole system px and converted internally:\n\n```html\n<vf-stack gap=\"12\"> <!-- a column -->\n <vf-stack fill-width direction=\"row\" gap=\"8\"> <!-- a labeled field -->\n <vf-label width=\"80\" for=\"name\">Name:</vf-label>\n <vf-text-field id=\"name\" fill-width></vf-text-field>\n </vf-stack>\n <vf-stack fill-width place=\"end\"> <!-- an action row -->\n <vf-button-group>\n <vf-button>Cancel</vf-button>\n <vf-button variant=\"default\">Save</vf-button>\n </vf-button-group>\n </vf-stack>\n</vf-stack>\n```\n\n**Why this can't just be page CSS.** Scaling is default-on and *per\ncomponent*: `ScaleController` sets `--vf-scale` on the component's own host,\nnot on the document. So `var(--vf-scale, 1)` in a consumer's stylesheet\nresolves only where the rule's element happens to sit inside a `vf-*`\nancestor and inherit it — true inside a window body, false for a plain\n`<div>` holding two buttons on an ordinary page, where the fallback `1` wins\nand the gap renders at 8px around 3×-sized buttons with no warning. A page\nthat hasn't called `applyScale()` has no way to write \"8 system px\" at all.\nA component always can, because it *is* the scope — which is also why this\none carries a ScaleController of its own: without it a lone stack\nwould resolve its own gap against that same fallback while its children each\nself-scaled around it.\n\n**Whole system px is the only expressible value**, so the gap half of the\nlayout contract (docs/SIZING.md rule 2) stops being a rule to remember. Declaring\n`width`/`height` covers the size half of rule 3 as well.\n\n**The geometry is governed by the content.** A column is as wide as its\nwidest child and a row as tall as its tallest; children keep the size they\ndrew themselves at (`flex: 0 0 auto` — no growing, no shrinking). System 7\nboxes are the size they are: a push button is as wide as its label, a popup\nmenu hugs its widest option, and a window is a fixed box whose overflow is\nclipped at the frame, not a layout that squeezes its controls to fit. The\nstack distributes; it does not resize. That is why the box shrink-wraps\n(`fit-content`) rather than claiming its parent's whole width — a layout box\nthat did would be inventing a size nobody declared.\n\n**`fill-width` / `fill-height` are how a child asks for more**, as bare\nattributes on consumer DOM the way `nosnap` opts an element out of snapping:\n\n```html\n<vf-text-field fill-width></vf-text-field>\n```\n\nEach names the *outcome*, not the axis, so the markup means the same thing\nwherever it lands; the stack does the flexbox translation, which is the whole\nreason to have a component. One rule to learn, about geometry rather than\nvocabulary: **the cross axis always has a size, the main axis only has slack\nif you declared one.** So `fill-width` always works in a column (the width is\nthe widest child's) and needs a declared `width` in a row; `fill-height` is\nexactly the other way round. A fill with nothing to take is inert, not an\nerror. Two children filling along the main axis end up *equal* — the zeroed\nflex basis is what lets them divide the slack rather than keep their natural\nsizes — and a child that declares its own size shouldn't also ask to fill it.\n\nA stack reads the same two attributes about *itself*, for the parents that\naren't stacks: a window body, a fieldset, a scroll well, a grid cell. That is\nwhere a panel's width enters the tree, and from there `fill-width` hands it\ndown a level at a time.\n\n**It paints nothing and means nothing.** No border, no background, no role,\nno keyboard behavior — what it holds decides what it is, as with\nVfGrid.\n\n**It holds its box on the device-pixel grid** — the `vf-container`\narrangement, adopted here after first shipping without it. The original\nreasoning (\"no ink of its own, and slotted `vf-*` children correct their own\norigins\") accounted only for kit children: a stack is a positioned ancestor\nand a layout box for *anything*, and consumer content inside it — a div, an\n`<img>`, a run of text — has no controller of its own. So the flex\ncontainer, the placed-child anchor and the `vf-snap` class live on one\nshadow box, and the correction moves the stack's\nwhole coordinate system; children (kit or not) ride it, and the sweep's\noutermost-first order means the kit children then find nothing left to\ncorrect. What this deliberately does not fix: a text-governed child width\nmid-row still shifts later siblings fractionally (their own controllers\ncover that).\n\n**Centering lands on whole system px as well** — halving an odd count of free\nsystem px gives a half, which no container can round in CSS. See\nCrossCenterController.\n\nIt is also the kit's one **typographically transparent** component: `vfBase`'s\nchrome face, ratio line box, color and non-selectability are all reset to\n`inherit` on the host. Wrapping content in a layout box must not change how\nthat content reads — inside a window it goes on inheriting the window's face\nexactly as before, and on a plain page it leaves the page's typography (and\nits whole-pixel line boxes) alone.\n\nWhat it deliberately does **not** do: equalize its children's widths. A row of\nbuttons still belongs in a `vf-button-group`, which sizes them all to the\nwidest and aligns their *faces* rather than the `variant=\"default\"` ring\nboxes a plain flex row would line up.",
13078
13358
  "name": "VfStack",
13079
13359
  "slots": [
13080
13360
  {
@@ -13119,7 +13399,7 @@
13119
13399
  "type": {
13120
13400
  "text": "VfStackPlace | undefined"
13121
13401
  },
13122
- "description": "Where the children sit across the stack — `start`, `center` or `end`.\nUnset resolves per direction: `start` down a column, `center` across a row.\n\nNamed `place` rather than `align` for a reason worth keeping in the source:\n`align` is a legacy HTML presentation attribute, and Blink maps it to\n`text-align` on any element, so the cross-axis switch used to re-align every\nrun of copy inside the stack (see the `text-align` reset above).\n\nNote the one thing centering cannot do: land on a whole pixel by itself. A\n16px caption centered in a row set by the 25-system-px `vf-number-field`\nsits at 4.5 system px, and no container can round that it would have to\nread each child's height. `applyGridSnap()` keeps the caption's own ink\ncrisp regardless (it corrects the origin inside the child's shadow root);\n`place=\"start\"` is the deterministic escape.",
13402
+ "description": "Where the children sit across the stack — `start`, `center` or `end`.\nUnset resolves per direction: `start` down a column, `center` across a row.\n\nNamed `place` rather than `align` for a reason worth keeping in the source:\n`align` is a legacy HTML presentation attribute, and Blink maps it to\n`text-align` on any element, so the cross-axis switch used to re-align every\nrun of copy inside the stack (see the `text-align` reset above).\n\nCentering divides the free space in two, so an odd count of system px would\nland a child on a half a 16px caption centered in a row set by the\n25-system-px `vf-number-field` sits at 4.5. CrossCenterController\nsteps that back onto whole system px, the exact half going toward the start\nthe way QuickDraw's `div 2` did; `place=\"start\"` is still the way to ask\nfor no centering at all.",
13123
13403
  "attribute": "place",
13124
13404
  "reflects": true
13125
13405
  },
@@ -13137,7 +13417,7 @@
13137
13417
  "privacy": "private",
13138
13418
  "readonly": true,
13139
13419
  "default": "new GridSnapController(this)",
13140
- "description": "Hold the box on the device-pixel grid under `applyGridSnap()` — see the\nclass doc for why the original no-controller decision was reversed. The\nhost is what gets measured; `.box` (vf-snap) is where the correction\nlands, taking the whole coordinate system with it."
13420
+ "description": "Hold the box on the device-pixel grid — see the\nclass doc for why the original no-controller decision was reversed. The\nhost is what gets measured; `.box` (vf-snap) is where the correction\nlands, taking the whole coordinate system with it."
13141
13421
  },
13142
13422
  {
13143
13423
  "kind": "field",
@@ -13149,6 +13429,34 @@
13149
13429
  "readonly": true,
13150
13430
  "description": "The shadow flex box `gap`/`pad` write to; exists from the first render."
13151
13431
  },
13432
+ {
13433
+ "kind": "field",
13434
+ "name": "childSlot",
13435
+ "type": {
13436
+ "text": "HTMLSlotElement"
13437
+ },
13438
+ "privacy": "private",
13439
+ "readonly": true,
13440
+ "description": "The slot the children arrive through. (`slot` itself is HTMLElement's.)"
13441
+ },
13442
+ {
13443
+ "kind": "field",
13444
+ "name": "crossCenter",
13445
+ "privacy": "private",
13446
+ "readonly": true,
13447
+ "default": "new CrossCenterController(this, () => ({ box: this.box, slot: this.childSlot, axis: this.centersChildren ? this.direction === 'row' ? 'y' : 'x' : null, }))",
13448
+ "description": "Keeps cross-axis centering on whole system px (src/cross-center.ts).\nA null axis is the inert state — no observers, nothing held — which is\nevery column that starts its children, i.e. the default."
13449
+ },
13450
+ {
13451
+ "kind": "field",
13452
+ "name": "centersChildren",
13453
+ "type": {
13454
+ "text": "boolean"
13455
+ },
13456
+ "privacy": "private",
13457
+ "description": "Mirrors the stylesheet's alignment selectors: a row centers unless `place`\nnames something else, a column only when it says `center`, and an\nunrecognized value lands on the direction's default in both places.",
13458
+ "readonly": true
13459
+ },
13152
13460
  {
13153
13461
  "kind": "field",
13154
13462
  "name": "width",
@@ -13234,7 +13542,7 @@
13234
13542
  "type": {
13235
13543
  "text": "VfStackPlace | undefined"
13236
13544
  },
13237
- "description": "Where the children sit across the stack — `start`, `center` or `end`.\nUnset resolves per direction: `start` down a column, `center` across a row.\n\nNamed `place` rather than `align` for a reason worth keeping in the source:\n`align` is a legacy HTML presentation attribute, and Blink maps it to\n`text-align` on any element, so the cross-axis switch used to re-align every\nrun of copy inside the stack (see the `text-align` reset above).\n\nNote the one thing centering cannot do: land on a whole pixel by itself. A\n16px caption centered in a row set by the 25-system-px `vf-number-field`\nsits at 4.5 system px, and no container can round that it would have to\nread each child's height. `applyGridSnap()` keeps the caption's own ink\ncrisp regardless (it corrects the origin inside the child's shadow root);\n`place=\"start\"` is the deterministic escape.",
13545
+ "description": "Where the children sit across the stack — `start`, `center` or `end`.\nUnset resolves per direction: `start` down a column, `center` across a row.\n\nNamed `place` rather than `align` for a reason worth keeping in the source:\n`align` is a legacy HTML presentation attribute, and Blink maps it to\n`text-align` on any element, so the cross-axis switch used to re-align every\nrun of copy inside the stack (see the `text-align` reset above).\n\nCentering divides the free space in two, so an odd count of system px would\nland a child on a half a 16px caption centered in a row set by the\n25-system-px `vf-number-field` sits at 4.5. CrossCenterController\nsteps that back onto whole system px, the exact half going toward the start\nthe way QuickDraw's `div 2` did; `place=\"start\"` is still the way to ask\nfor no centering at all.",
13238
13546
  "fieldName": "place"
13239
13547
  },
13240
13548
  {
@@ -13436,7 +13744,7 @@
13436
13744
  "privacy": "private",
13437
13745
  "readonly": true,
13438
13746
  "default": "new GridSnapController(this)",
13439
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
13747
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
13440
13748
  },
13441
13749
  {
13442
13750
  "kind": "field",
@@ -14429,7 +14737,7 @@
14429
14737
  "privacy": "protected",
14430
14738
  "readonly": true,
14431
14739
  "default": "new GridSnapController(this)",
14432
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts.",
14740
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts.",
14433
14741
  "inheritedFrom": {
14434
14742
  "name": "VfTextControlBase",
14435
14743
  "module": "src/text-control.ts"
@@ -15527,7 +15835,7 @@
15527
15835
  "privacy": "protected",
15528
15836
  "readonly": true,
15529
15837
  "default": "new GridSnapController(this)",
15530
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts.",
15838
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts.",
15531
15839
  "inheritedFrom": {
15532
15840
  "name": "VfTextControlBase",
15533
15841
  "module": "src/text-control.ts"
@@ -16578,7 +16886,7 @@
16578
16886
  "privacy": "private",
16579
16887
  "readonly": true,
16580
16888
  "default": "new GridSnapController(this)",
16581
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
16889
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
16582
16890
  },
16583
16891
  {
16584
16892
  "kind": "field",
@@ -18058,7 +18366,7 @@
18058
18366
  "privacy": "protected",
18059
18367
  "readonly": true,
18060
18368
  "default": "new GridSnapController(this)",
18061
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
18369
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
18062
18370
  },
18063
18371
  {
18064
18372
  "kind": "field",
@@ -18950,7 +19258,7 @@
18950
19258
  "privacy": "private",
18951
19259
  "readonly": true,
18952
19260
  "default": "new GridSnapController(this)",
18953
- "description": "Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts."
19261
+ "description": "Device-pixel grid snapping; see src/grid-snap.ts."
18954
19262
  },
18955
19263
  {
18956
19264
  "kind": "field",
@@ -19487,7 +19795,7 @@
19487
19795
  },
19488
19796
  {
19489
19797
  "kind": "mixin",
19490
- "description": "Explicit placement — `top`/`left` in whole system px, on (nearly) every\ncomponent.\n\nA DITL resource laid a dialog out as a list of items, each with a rectangle\nin the window's own coordinates; arranging controls by stating where they go\nis as native to System 7 as stacking them. This mixin is that mechanism:\ndeclaring `top` or `left` takes the element out of normal flow and\nabsolutely positions it within its parent, both coordinates in the art's own\nunit so the position scales with the display like every other metric. A\nwindow or dialog body can be laid out either way — a tree of `vf-stack`s, or\npositioned children — with no stylesheet in either case.\n\nMechanics, mirroring how `vf-stack` writes its declared size:\n\n- Setting either property writes `position: absolute` and both offsets onto\n the host's inline style as `calc(var(--vf-scale, 1) * Npx)` — live against\n the display, resolved at paint time. The coordinate left unset is 0.\n- `right`/`bottom` are released to `auto` and `margin` zeroed, so the stated\n offsets are the whole story — an auto-width box with both edges set would\n stretch rather than sit (the `vf-icon` seed logic, generalized).\n- Unsetting both returns the element to normal flow: every inline\n declaration this wrote is removed, and stylesheet values resume.\n\nThe anchor is CSS's own: the nearest positioned ancestor's padding box. The\nkit's containers are all deliberate anchors — a desktop's raster, a window's\ncontent region (the frame's inner edge, below the title bar — the 12px body\ninset governs flow content only, exactly the DITL convention), a dialog's\ncontent area, a stack's box, a fieldset's border box, a scroll area's\nscrolled plane. In a non-kit parent, give the parent `position: relative`,\nthe one line of CSS this feature can't write for you.\n\nThe writing rides a ReactiveController (`hostUpdated`) rather than\nan `updated()` override, for a lifecycle reason worth keeping in the source:\nthe mixin sits *under* each component class, and most components override\n`updated()` without calling `super.updated()` — Lit's base is a no-op, so\nnothing ever forced the call — which would silently shadow a mixin-level\noverride. A controller is invoked by ReactiveElement itself, after every\nupdate, no matter what the subclass does.\n\nA gesture writes **through these properties**: `vf-window`'s title-bar drag\nand `vf-icon`'s drag and arrow nudge hand their new origin to a\nPlacementController, which snaps it onto the placement lattice and\nsets `left`/`top`. A moved element is therefore placed exactly the way an\nauthored one is — a live `calc()` in the art's own unit — and stays where it\nwas dropped when the zoom or the display changes what a system px costs.\n\nIt did not always. Writing the resolved CSS px straight to the inline style\nfroze the coordinate in the wrong unit: `--vf-scale` moved under it at every\nzoom step and the same constant read back as a different number of system px,\nso moved windows and icons slid off the grid the rest of the kit stayed on\n(by `3z / round(3z)` — 10% at 110% zoom, where nothing else moves at all).\n\nSetting a property yourself is still the deliberate way to re-place a moved\nelement. The controller re-applies **only when the values changed**, so an\nunrelated update — a heading change, a desktop toggling `active` — never\nre-asserts a coordinate and costs nothing.",
19798
+ "description": "Explicit placement — `top`/`left` in whole system px, on **every** component.\n\nNo exceptions, deliberately. These are web components, and a consumer may put\none wherever they like; the kit does not get to decide that a `vf-option` is\nonly ever a popup row. The rows a container normally owns (`vf-option`,\n`vf-menu-item`, `vf-list-item`) and a bar's `vf-menu` take the pair on the\nsame terms as anything else — and stating an origin on one *inside* its\nmanaging parent does take it out of that parent's flow layout, which is the\nplacement working, not failing. Each of those components documents what its\ncontainer stops doing for it. `vf-dialog` takes the pair in viewport\ncoordinates, the one difference the platform forces (see modal-dialog.ts).\n\nA DITL resource laid a dialog out as a list of items, each with a rectangle\nin the window's own coordinates; arranging controls by stating where they go\nis as native to System 7 as stacking them. This mixin is that mechanism:\ndeclaring `top` or `left` takes the element out of normal flow and\nabsolutely positions it within its parent, both coordinates in the art's own\nunit so the position scales with the display like every other metric. A\nwindow or dialog body can be laid out either way — a tree of `vf-stack`s, or\npositioned children — with no stylesheet in either case.\n\nMechanics, mirroring how `vf-stack` writes its declared size:\n\n- Setting either property writes `position: absolute` and both offsets onto\n the host's inline style as `calc(var(--vf-scale, 1) * Npx)` — live against\n the display, resolved at paint time. The coordinate left unset is 0.\n- `right`/`bottom` are released to `auto` and `margin` zeroed, so the stated\n offsets are the whole story — an auto-width box with both edges set would\n stretch rather than sit (the `vf-icon` seed logic, generalized).\n- Unsetting both returns the element to normal flow: every inline\n declaration this wrote is removed, and stylesheet values resume.\n\nThe anchor is CSS's own: the nearest positioned ancestor's padding box. The\nkit's containers are all deliberate anchors — a desktop's raster, a window's\ncontent region (the frame's inner edge, below the title bar — the 12px body\ninset governs flow content only, exactly the DITL convention), a dialog's\ncontent area, a stack's box, a fieldset's border box, a scroll area's\nscrolled plane. In a non-kit parent, give the parent `position: relative`,\nthe one line of CSS this feature can't write for you.\n\nThe writing rides a ReactiveController (`hostUpdated`) rather than\nan `updated()` override, for a lifecycle reason worth keeping in the source:\nthe mixin sits *under* each component class, and most components override\n`updated()` without calling `super.updated()` — Lit's base is a no-op, so\nnothing ever forced the call — which would silently shadow a mixin-level\noverride. A controller is invoked by ReactiveElement itself, after every\nupdate, no matter what the subclass does.\n\nA gesture writes **through these properties**: `vf-window`'s title-bar drag\nand `vf-icon`'s drag and arrow nudge hand their new origin to a\nPlacementController, which snaps it onto the placement lattice and\nsets `left`/`top`. A moved element is therefore placed exactly the way an\nauthored one is — a live `calc()` in the art's own unit — and stays where it\nwas dropped when the zoom or the display changes what a system px costs.\n\nIt did not always. Writing the resolved CSS px straight to the inline style\nfroze the coordinate in the wrong unit: `--vf-scale` moved under it at every\nzoom step and the same constant read back as a different number of system px,\nso moved windows and icons slid off the grid the rest of the kit stayed on\n(by `3z / round(3z)` — 10% at 110% zoom, where nothing else moves at all).\n\nSetting a property yourself is still the deliberate way to re-place a moved\nelement. The controller re-applies **only when the values changed**, so an\nunrelated update — a heading change, a desktop toggling `active` — never\nre-asserts a coordinate and costs nothing.",
19491
19799
  "name": "VfPositioned",
19492
19800
  "members": [
19493
19801
  {