vintage-frames 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +212 -6
- package/dist/components/vf-icon-field.d.ts +29 -1
- package/dist/components/vf-icon-field.js +93 -34
- package/dist/components/vf-icon.d.ts +58 -6
- package/dist/components/vf-icon.js +228 -192
- package/dist/index.d.ts +3 -2
- package/dist/index.js +42 -42
- package/dist/motion.d.ts +31 -0
- package/dist/motion.js +15 -1
- package/dist/open-art.d.ts +24 -11
- package/dist/open-art.js +18 -16
- package/dist/position.d.ts +10 -0
- package/dist/position.js +8 -1
- package/docs/SPEC.md +8 -6
- package/editor/vscode.html-custom-data.json +2 -2
- package/editor/web-types.json +3 -3
- package/package.json +1 -1
package/custom-elements.json
CHANGED
|
@@ -4598,7 +4598,7 @@
|
|
|
4598
4598
|
"declarations": [
|
|
4599
4599
|
{
|
|
4600
4600
|
"kind": "class",
|
|
4601
|
-
"description": "`<vf-icon-field>` — the container a field of `vf-icon`s sits in: a desktop's\nicons, a folder window's contents.\n\nEvery other set in the kit has its container — `vf-list` owns\n`vf-list-item`, `vf-radio-group` owns `vf-radio`, `vf-menu` owns\n`vf-menu-item`, `vf-select` owns `vf-option` — and `vf-icon` was the one\nitem whose container the consumer wrote by hand: a bare\n`<div role=\"listbox\" aria-multiselectable=\"true\">`, because an `option` is\nonly valid inside a `listbox` that owns it, and every kit container is a\npositioning anchor by design, so nothing in the kit was neutral enough to\nstand in. A consumer who forgot the role got icons that silently degraded\nto pictures. This is that container, as kit furniture:\n\n```html\n<vf-desktop width=\"512\" height=\"342\">\n <vf-icon-field label=\"Desktop\" fill-width fill-height>\n <vf-icon label=\"Macintosh HD\" width=\"64\" selectable movable editable left=\"16\" top=\"24\">…</vf-icon>\n <vf-icon label=\"Trash\" width=\"64\" selectable movable editable left=\"16\" top=\"280\">…</vf-icon>\n </vf-icon-field>\n <vf-window heading=\"Documents\" width=\"320\" height=\"201\" scrollbars=\"both\" top=\"40\" left=\"120\">\n <vf-icon-field label=\"Documents\">\n <vf-icon label=\"Read Me\" width=\"64\" selectable movable editable left=\"16\" top=\"16\">…</vf-icon>\n </vf-icon-field>\n </vf-window>\n</vf-desktop>\n```\n\n### Semantics through internals\n\n`role=\"listbox\"` and `aria-multiselectable=\"true\"` always — the Finder was\nalways multi-select, so there is no attribute for it — and `label` as the\naccessible name, the way `vf-scroll-area` takes one. Internals values are\ndefaults (SPEC §2): a consumer's own `role`/`aria-*` on the tag still wins.\nA `selectable` icon inside becomes a real `option` carrying `aria-selected`;\n`vf-icon` matches this tag directly, since a role written through internals\nnever lands as an attribute for `closest()` to find.\n\n### Layout-neutral, exactly what the div was\n\nAn in-flow block with no size, no inset and no position of its own,\npainting nothing — the `vf-stack` posture. A field whose icons are all\nplaced is a zero-height block, so the icons keep anchoring to the desktop's\nraster or the window's plane, stored coordinates mean what they meant, and\na press on the bare dither still reaches the desktop. It takes `top`/`left`\nand `width`/`height` like the other declared-box containers, so a page that\nwants the field to be a box places and sizes it, at which point it becomes\nthe anchor — the placement working, as documented for everything.\n`fill-width`/`fill-height` are the other way to give it a box, read about\nthe host the way `vf-stack` and `vf-container` read them: a filled field\nstays in flow and static, so its icons keep their anchor, and it has a\nsurface to press — which is what the rubber band needs.\n\n### The rubber band\n\nA press on the field's own background — not on an icon — dragged across\nthe field draws the Finder's dotted selection rectangle from the press to\nthe pointer, and the selection follows it live by the Finder's one rule:\n**an icon is selected exactly when it was selected at the press XOR the\nrectangle touches it.** The press fixes that anchor: a plain press has\nalready cleared everything (the way any press outside an icon does), so\nthe rectangle simply selects what it touches and releases what it leaves;\nwith Shift or ⌘ held the selection survives the press and the rectangle\n*toggles* against it — an icon already selected deselects while the\nrectangle covers it and comes back when it leaves, one that was not\nselects. Each change reports `vf-select` as a press would. \"Touches\"\nmeans the icon's art cell or its name plate (VfIcon.touches), not\nthe empty cell beside them, so a rectangle through the gap between two\nicons selects neither. The release keeps the result; Escape or a\n`pointercancel` cancels, the rectangle vanishes and the selection goes\nback to the anchor. A press that never leaves its system pixel draws\nnothing.\n\nThe rectangle is drawn with the drag outline's pen (* paintSelectionRect: the dotted one-pixel outline, `filter: invert(1)`\nunder `mix-blend-mode: difference`) at the field's own level, which is\nwhere the Finder drew its marquee — on the desktop under the windows, in a\nwindow body over that window's content and under any window above it —\npositioned against the viewport from the field's own shadow, since a\nlayout-neutral field has no box of its own to anchor to. It reaches no\nfurther than the field, inside every clip above the field (a window body,\na scroll viewport, the raster's edge), so it never paints outside the\nregion the icons are visible in. Its dots take the pen's phase on the\n*screen* — the desktop's, found by the `vf-drag-surface-request`\nhandshake the drag outline uses (penPhase) — so over the desktop\ndither the rectangle reads as a black line wherever the field sits; with\nno desktop, the field's own box is the reference.\n\nOnly a field with a box can be pressed: fill it, or place and size it. An\nunplaced, unfilled field of placed icons is zero-height and draws no band.\n`touch-action` is left alone, so on a touch screen a drag on the\nbackground pans a scrolling window as it always did; the band is a mouse\nand pen gesture.\n\n### `size` is the view's\n\n`large` or `small`, written onto every icon in the field when set and\nwhenever it changes, so View → by Small Icon is one attribute instead of a\nloop over every icon. Unset, each icon keeps its own.\n\n### The desktop uses it, and renders none\n\nA desktop's icons sit in a field slotted into `vf-desktop` beside the\nwindows, written by the page; a folder window's icons sit in one slotted\ninto the window body. `vf-desktop` builds no field of its own: its furniture\nis slotted light DOM and the page decides what it is, a desktop with no\nicons should carry no empty listbox, and a field inside the desktop's\nshadow is one `closest()` could not reach.",
|
|
4601
|
+
"description": "`<vf-icon-field>` — the container a field of `vf-icon`s sits in: a desktop's\nicons, a folder window's contents.\n\nEvery other set in the kit has its container — `vf-list` owns\n`vf-list-item`, `vf-radio-group` owns `vf-radio`, `vf-menu` owns\n`vf-menu-item`, `vf-select` owns `vf-option` — and `vf-icon` was the one\nitem whose container the consumer wrote by hand: a bare\n`<div role=\"listbox\" aria-multiselectable=\"true\">`, because an `option` is\nonly valid inside a `listbox` that owns it, and every kit container is a\npositioning anchor by design, so nothing in the kit was neutral enough to\nstand in. A consumer who forgot the role got icons that silently degraded\nto pictures. This is that container, as kit furniture:\n\n```html\n<vf-desktop width=\"512\" height=\"342\">\n <vf-icon-field label=\"Desktop\" fill-width fill-height>\n <vf-icon label=\"Macintosh HD\" width=\"64\" selectable movable editable left=\"16\" top=\"24\">…</vf-icon>\n <vf-icon label=\"Trash\" width=\"64\" selectable movable editable left=\"16\" top=\"280\">…</vf-icon>\n </vf-icon-field>\n <vf-window heading=\"Documents\" width=\"320\" height=\"201\" scrollbars=\"both\" top=\"40\" left=\"120\">\n <vf-icon-field label=\"Documents\">\n <vf-icon label=\"Read Me\" width=\"64\" selectable movable editable left=\"16\" top=\"16\">…</vf-icon>\n </vf-icon-field>\n </vf-window>\n</vf-desktop>\n```\n\n### Semantics through internals\n\n`role=\"listbox\"` and `aria-multiselectable=\"true\"` always — the Finder was\nalways multi-select, so there is no attribute for it — and `label` as the\naccessible name, the way `vf-scroll-area` takes one. Internals values are\ndefaults (SPEC §2): a consumer's own `role`/`aria-*` on the tag still wins.\nA `selectable` icon inside becomes a real `option` carrying `aria-selected`;\n`vf-icon` matches this tag directly, since a role written through internals\nnever lands as an attribute for `closest()` to find.\n\n### Layout-neutral, exactly what the div was\n\nAn in-flow block with no size, no inset and no position of its own,\npainting nothing — the `vf-stack` posture. A field whose icons are all\nplaced is a zero-height block, so the icons keep anchoring to the desktop's\nraster or the window's plane, stored coordinates mean what they meant, and\na press on the bare dither still reaches the desktop. It takes `top`/`left`\nand `width`/`height` like the other declared-box containers, so a page that\nwants the field to be a box places and sizes it, at which point it becomes\nthe anchor — the placement working, as documented for everything.\n`fill-width`/`fill-height` are the other way to give it a box, read about\nthe host the way `vf-stack` and `vf-container` read them: a filled field\nstays in flow and static, so its icons keep their anchor, and it has a\nsurface to press — which is what the rubber band needs.\n\n### The rubber band\n\nA press on the field's own background — not on an icon — dragged across\nthe field draws the Finder's dotted selection rectangle from the press to\nthe pointer, and the selection follows it live by the Finder's one rule:\n**an icon is selected exactly when it was selected at the press XOR the\nrectangle touches it.** The press fixes that anchor: a plain press has\nalready cleared everything (the way any press outside an icon does), so\nthe rectangle simply selects what it touches and releases what it leaves;\nwith Shift or ⌘ held the selection survives the press and the rectangle\n*toggles* against it — an icon already selected deselects while the\nrectangle covers it and comes back when it leaves, one that was not\nselects. Each change reports `vf-select` as a press would. \"Touches\"\nmeans the icon's art cell or its name plate (VfIcon.touches), not\nthe empty cell beside them, so a rectangle through the gap between two\nicons selects neither. The release keeps the result; Escape or a\n`pointercancel` cancels, the rectangle vanishes and the selection goes\nback to the anchor. A press that never leaves its system pixel draws\nnothing.\n\nThe rectangle is drawn with the drag outline's pen (* paintSelectionRect: the dotted one-pixel outline, `filter: invert(1)`\nunder `mix-blend-mode: difference`) at the field's own level, which is\nwhere the Finder drew its marquee — on the desktop under the windows, in a\nwindow body over that window's content and under any window above it —\npositioned against the viewport from the field's own shadow, since a\nlayout-neutral field has no box of its own to anchor to. It reaches no\nfurther than the field, inside every clip above the field (a window body,\na scroll viewport, the raster's edge), so it never paints outside the\nregion the icons are visible in. Its dots take the pen's phase on the\n*screen* — the desktop's, found by the `vf-drag-surface-request`\nhandshake the drag outline uses (penPhase) — so over the desktop\ndither the rectangle reads as a black line wherever the field sits; with\nno desktop, the field's own box is the reference.\n\nOnly a field with a box can be pressed: fill it, or place and size it. An\nunplaced, unfilled field of placed icons is zero-height and draws no band.\n`touch-action` is left alone, so on a touch screen a drag on the\nbackground pans a scrolling window as it always did; the band is a mouse\nand pen gesture.\n\n### The walk\n\nThe Finder's Clean Up moved a container's icons one at a time, each\nicon's dotted outline travelling to its cell and the icon landing when\nthe outline arrived. dragIcons is that walk: `{ icon, left, top }`\nmoves in the page's order — which icon goes first is a statement about\nthe page's lattice, and the kit ships none — each through the icon's own\n`dragTo`, with a beat (`WALK_BEAT_MS`, src/motion.ts) after every landing\nthat showed. It resolves when the last icon has landed. One walk per\nfield: a second call finishes the first. A press anywhere, or Escape,\nfinishes it too — every icon still to go lands at its target at once, the\nFinder never made you wait — and so does the field leaving the DOM; an\nicon gone from the DOM by its turn is skipped. Under\n`prefers-reduced-motion` every icon lands at once, no outline, no beat.\n\n### `size` is the view's\n\n`large` or `small`, written onto every icon in the field when set and\nwhenever it changes, so View → by Small Icon is one attribute instead of a\nloop over every icon. Unset, each icon keeps its own.\n\n### The desktop uses it, and renders none\n\nA desktop's icons sit in a field slotted into `vf-desktop` beside the\nwindows, written by the page; a folder window's icons sit in one slotted\ninto the window body. `vf-desktop` builds no field of its own: its furniture\nis slotted light DOM and the page decides what it is, a desktop with no\nicons should carry no empty listbox, and a field inside the desktop's\nshadow is one `closest()` could not reach.",
|
|
4602
4602
|
"name": "VfIconField",
|
|
4603
4603
|
"slots": [
|
|
4604
4604
|
{
|
|
@@ -4660,6 +4660,34 @@
|
|
|
4660
4660
|
"default": "new DocumentListenersController(this, () => [ [document, 'keydown', this.#onKeyDown, true], ])",
|
|
4661
4661
|
"description": "Escape mid-band cancels — the in-flight-gesture idiom `vf-icon`'s drag\nuses, on the document because the field never takes focus. Capture\nphase, and the key is stopped there, so an enclosing `vf-dialog` never\nreads it as a dismissal."
|
|
4662
4662
|
},
|
|
4663
|
+
{
|
|
4664
|
+
"kind": "field",
|
|
4665
|
+
"name": "#walk",
|
|
4666
|
+
"privacy": "private",
|
|
4667
|
+
"type": {
|
|
4668
|
+
"text": "Walk | null"
|
|
4669
|
+
},
|
|
4670
|
+
"default": "null",
|
|
4671
|
+
"description": "The walk in flight — a page's dragIcons — or null."
|
|
4672
|
+
},
|
|
4673
|
+
{
|
|
4674
|
+
"kind": "field",
|
|
4675
|
+
"name": "#interrupt",
|
|
4676
|
+
"privacy": "private",
|
|
4677
|
+
"readonly": true,
|
|
4678
|
+
"default": "new DocumentListenersController(this, () => [ [document, 'pointerdown', this.#onWalkPress, true], [document, 'keydown', this.#onWalkKeyDown, true], ])",
|
|
4679
|
+
"description": "A press anywhere, or Escape, finishes a walk in flight: every icon\nstill to go lands at once. Scoped to the walk, on the document, capture\nphase, the key stopped there — the band's Escape rule."
|
|
4680
|
+
},
|
|
4681
|
+
{
|
|
4682
|
+
"kind": "field",
|
|
4683
|
+
"name": "#onWalkPress",
|
|
4684
|
+
"privacy": "private"
|
|
4685
|
+
},
|
|
4686
|
+
{
|
|
4687
|
+
"kind": "field",
|
|
4688
|
+
"name": "#onWalkKeyDown",
|
|
4689
|
+
"privacy": "private"
|
|
4690
|
+
},
|
|
4663
4691
|
{
|
|
4664
4692
|
"kind": "field",
|
|
4665
4693
|
"name": "#onSlotChange",
|
|
@@ -4877,6 +4905,73 @@
|
|
|
4877
4905
|
],
|
|
4878
4906
|
"description": "The selection under the band — the Finder's rule, re-evaluated for\nevery selectable icon on every changed rectangle: selected exactly when\nit was in the anchor XOR the rectangle touches it (its art cell or its\nplate, the icon's own reading). A plain press left an empty anchor, so\nthe rectangle selects what it touches and releases what it leaves; a\nShift or ⌘ press kept one, so the rectangle toggles against it. Each\nchange goes through the icon's own gesture route so `vf-select` reports\nit."
|
|
4879
4907
|
},
|
|
4908
|
+
{
|
|
4909
|
+
"kind": "method",
|
|
4910
|
+
"name": "dragIcons",
|
|
4911
|
+
"return": {
|
|
4912
|
+
"type": {
|
|
4913
|
+
"text": "Promise<void>"
|
|
4914
|
+
}
|
|
4915
|
+
},
|
|
4916
|
+
"parameters": [
|
|
4917
|
+
{
|
|
4918
|
+
"name": "moves",
|
|
4919
|
+
"type": {
|
|
4920
|
+
"text": "readonly VfIconMove[]"
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4923
|
+
],
|
|
4924
|
+
"description": "Walk the icons to their targets one at a time, in the order given (see\nthe class doc): each through its own VfIcon.dragTo, the beat\nafter every landing that showed. Resolves when the last has landed. A\nwalk already in flight is finished first."
|
|
4925
|
+
},
|
|
4926
|
+
{
|
|
4927
|
+
"kind": "method",
|
|
4928
|
+
"name": "#run",
|
|
4929
|
+
"privacy": "private",
|
|
4930
|
+
"return": {
|
|
4931
|
+
"type": {
|
|
4932
|
+
"text": "Promise<void>"
|
|
4933
|
+
}
|
|
4934
|
+
},
|
|
4935
|
+
"parameters": [
|
|
4936
|
+
{
|
|
4937
|
+
"name": "walk",
|
|
4938
|
+
"type": {
|
|
4939
|
+
"text": "Walk"
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
],
|
|
4943
|
+
"description": "The walk itself: a landing, a beat, the next."
|
|
4944
|
+
},
|
|
4945
|
+
{
|
|
4946
|
+
"kind": "method",
|
|
4947
|
+
"name": "#finishWalk",
|
|
4948
|
+
"privacy": "private",
|
|
4949
|
+
"return": {
|
|
4950
|
+
"type": {
|
|
4951
|
+
"text": "void"
|
|
4952
|
+
}
|
|
4953
|
+
},
|
|
4954
|
+
"description": "Finish the walk in flight, if any: every icon still to go — the one\ntravelling included, whose `moveTo` finishes its travel — lands at its\ntarget now, the beat is cut, the promise resolves."
|
|
4955
|
+
},
|
|
4956
|
+
{
|
|
4957
|
+
"kind": "method",
|
|
4958
|
+
"name": "#endWalk",
|
|
4959
|
+
"privacy": "private",
|
|
4960
|
+
"return": {
|
|
4961
|
+
"type": {
|
|
4962
|
+
"text": "void"
|
|
4963
|
+
}
|
|
4964
|
+
},
|
|
4965
|
+
"parameters": [
|
|
4966
|
+
{
|
|
4967
|
+
"name": "walk",
|
|
4968
|
+
"type": {
|
|
4969
|
+
"text": "Walk"
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
],
|
|
4973
|
+
"description": "The walk is over: the listeners down, the promise resolved."
|
|
4974
|
+
},
|
|
4880
4975
|
{
|
|
4881
4976
|
"kind": "field",
|
|
4882
4977
|
"name": "role",
|
|
@@ -5110,11 +5205,11 @@
|
|
|
5110
5205
|
"declarations": [
|
|
5111
5206
|
{
|
|
5112
5207
|
"kind": "class",
|
|
5113
|
-
"description": "`<vf-icon>` — a Finder icon: pixel art in a reserved cell with its name on a\nplate below, selectable, movable and renameable in place.\n\nIt is the one composite in the kit whose parts already existed separately —\n`vf-img` draws the art, `vf-list-item` inverts on selection, `vf-window`\ndrags — and the reason to have it anyway is that the *icon* is the unit the\nFinder actually manipulates. A picture and a caption that select together,\nmove together and rename together is a control, not a layout.\n\n### The art arrives by slot\n\nTwo slots, one per resource size — `large` is the 32×32 `ICN#` and `small`\nthe 16×16 `ics#` — each holding a `vf-img` around the consumer's own `<img>`:\n\n```html\n<vf-icon label=\"Macintosh HD\" selectable movable editable>\n <vf-img slot=\"large\"><img src=\"hd-32.png\" alt=\"\"></vf-img>\n <vf-img slot=\"small\"><img src=\"hd-16.png\" alt=\"\"></vf-img>\n</vf-icon>\n```\n\nSlots rather than `src` properties, for the reason `vf-img` exists at all:\nthe kit ships no raster files and never builds an `<img>` on a consumer's\nbehalf, so the graphic stays a real element in the light DOM with its own\n`alt`, `srcset`, loading behavior and asset URLs. A `src` string can express\nnone of that, and it could not hold an inline `<svg>` or a `<canvas>` either.\n`vf-list-item`'s `icon` slot makes the same trade.\n\nThe cost is that both files fetch even though one paints — pay it with two\ndata URIs, or slot only the size that view uses.\n\n### The cell is reserved, the art is registered in it\n\n`size` picks which slot paints *and* the cell it paints in: 32×32 or 16×16,\nheld whether or not there is art for it. A folder is 32×20 of ink and a\ndocument 25×32, and a row of them keeps one baseline only because the cell —\nnot the ink — is the unit, which is what an icon resource always was.\n\n### Everything centered lands on a whole pixel, by parity\n\nThe frame centers two things over one axis — the art cell and the name — and\na centered child sits at `(box − child) / 2`. That is a whole number exactly\nwhen the box and the child have the **same parity**. Half a system pixel is\nwhat fringes 1-bit art: the glyph stems smear across two device columns and\ngo gray, while the plate behind them stays sharp, because backgrounds are\npixel-snapped by the compositor and glyphs are not. A crisp plate under a\ngrey name is the signature of exactly this.\n\nSo the component makes the parities agree instead of correcting afterwards.\nThe cell is 32 or 16, and #measurePlate sizes the name plate to a\nwhole **even** number of system px — measured from the text, rounded up. Both\nchildren are then even, and so is every offset, at every display density and\nwhether or not a `width` is declared. Nothing is snapped, nothing leans on\nthe rasterizer, and `verify:icon` asserts both the geometry and that a field\nof names renders without a single gray pixel — with the kit's normal\nantialiasing left on, since a run on whole pixels gives it nothing to smooth.\n\nYour side of it is one rule: **a declared `width` must be even**, since the\ncomponent cannot round a number you chose. Supply art at the cell size too —\nwhich \"one image pixel is one system pixel\" already implies — because art\ndiffering from its cell by an odd number centers the same way.\n\n### Selection inverts, because the art is a 1-bit mask\n\nA System 7 icon is black ink and opaque white on a transparent surround —\nprecisely an image plus its mask — so inverting it flips ink and fill and\nleaves the surround alone, which is the whole of the classic selected\nappearance. `filter: invert(1)` reproduces it exactly for 1-bit art. Color\nart would invert into a photographic negative, which was never what\nSystem 7 showed: declare `color` and selection **darkens** the art\ninstead — Icon Utilities' ttSelected transform, every color blended\nhalfway toward black (`brightness(0.5)`), whites going gray, the\ntransparent surround still untouched. The declaration is the consumer's\nbecause the component cannot tell color art from a mask by looking at a\nslotted image. It covers whatever the cell shows, the derived open ghost\nincluded, so a color icon has one selected treatment open or closed. The\nlabel plate inverts to the `--vf-highlight` pair either way, sharing one\nselection color with `vf-list-item` — the selected name was inverted\nwhatever the art.\n\n### Open is derived, not shipped\n\nWith `open`, the art redraws as the Finder's open ghost: the outline held\nin solid black, the interior re-filled with the kit's loose 25% dither (the\nscrollbar trough's lattice), the transparent surround untouched. There is\nno second raster and no second fetch — the same alpha channel that makes\nselection an inversion makes the ghost derivable, and `src/open-art.ts`\nderives it from the slotted art by canvas compositing alone. No pixels are\never read back, so a cross-origin image that taints its canvas still works:\ntaint forbids reading, not drawing or displaying.\n\nThe ghost keeps the shape selection expects — ink and opaque white on a\ntransparent surround — so a selected open icon inverts exactly as a closed\none does, with no second treatment. The slot stays in the tree while the\nghost paints, hidden (it is where the art loads, and re-loads, from), and\nart the pipeline cannot draw — nothing slotted yet, a failed load, an\ninline `<svg>` — keeps rendering as itself rather than vanishing behind a\nstate it cannot show.\n\n### `movable`, not `draggable`\n\n`draggable` is a global HTML attribute *and* an `HTMLElement` accessor, so\ndeclaring it would both shadow a platform member and hand the element to the\nbrowser's own drag-and-drop machinery. This is the `align` trap from\n`vf-stack` in a second costume — `align`, `hidden`, `dir`, `draggable` and\n`title` all carry behavior a custom element never asked for. The kit already\nspells this parameter `movable` on `vf-window`, so the icon does too, and it\nmoves the same way: `DragController` tracks the gesture and\n`PlacementController` writes the result into `left`/`top` in whole system px,\nthe art's own unit — the same pair markup places an icon with, so a moved\nicon is still where it was dropped after a zoom.\n\nDragging is a pointer gesture with no keyboard equivalent, which is the kind\nof gap the kit closes rather than inherits (SPEC §1): a focused movable icon\nalso moves under the arrow keys, one system px at a time and eight with\nShift. Focus is what `selectable` grants, so the keyboard half of `movable`\nand `editable` presupposes it — see the role section below.\n\n### The drag is an outline; the icon stays put\n\nThe Finder dragged an icon as a dotted outline — the mask's boundary and\nthe name's rectangle — drawn over everything with the XOR pen, the icon\nitself staying put until the drop. The kit draws it from what it already\nhas: deriveDragOutline finds the ring the way the open ghost does\n(the silhouette from the art's alpha, eroded by one pixel, the ring the\nerosion removes), adds the plate's rectangle, and dotOutline dots\nit against a 2×2 checker — compositing only, so cross-origin art derives\ntoo, and art the pipeline cannot draw falls back to the cell's rectangle.\nThe canvas paints with the cursor's XOR recipe, `filter: invert(1)` under\n`mix-blend-mode: difference`: a dotted black line over a white window\nbody, and over the desktop dither a black line, the composition\nQuickDraw's pattern pen gave — the checker's phase is taken from where\nthe outline lands on the *screen* (penPhase), so its dots fall on\nthe pixels the dither leaves white wherever it goes, and the dither's own\nink fills in between them.\n\nIt draws on the desktop's drag surface: a layer in the screen's shadow,\nover windows, palettes and the menu bar alike, clipped at the raster's\nedge like everything else, reached through the `vf-drag-surface-request`\nhandshake (bubbles, non-composed — it travels up the slots the icon is\nassigned to, so it works from inside a window body). With no desktop the\ncanvas lives in the icon's own frame instead, translated by the delta,\nclipped by whatever clips the icon; the gesture and its events are the\nsame either way. Its origin is a whole count of system px from the\nsurface's, written as a live `sysLength()`, so a scale change mid-gesture\ncannot smear it.\n\n### The drag reports, the page files\n\nThe kit ships no filing semantics — folders are the page's own catalog —\nso a drag is a stream of events and a cancelable commit, on the icon:\n\n- `vf-drag-start` `{ left, top }` on the first lattice step away from the\n press (a plain click never starts one, and never fires a drop);\n- `vf-drag` `{ clientX, clientY, left, top, x, y }` on every step that\n changed the snapped proposal;\n- `vf-drop`, the same shape at the release, **cancelable**: its default\n action is the move, the proposal written through `left`/`top` and\n clamped whole in the container measured at the press. `preventDefault()`\n writes nothing — the page re-parents or places the icon itself, the\n outline's origin converted with the destination's `placementAt`;\n- `vf-drag-cancel` `{}` on Escape or a `pointercancel`: nothing written.\n\n`left`/`top` in the detail are the *proposal*, unclamped — the origin plus\nthe lattice-snapped delta — and only the default action clamps, so a page\nthat cancels gets the raw gesture. `x`/`y` are the outline's top-left in\nviewport CSS px: the frame's box at the press translated by the delta,\nthe press offset already inside it, so an icon placed at the\ndestination's `placementAt(x, y)` lands exactly where the outline was.\n\n### The selection travels\n\nA drag that begins on a *selected* icon takes every other selected,\nmovable icon of its field with it — the Finder outlined the whole\nselection under a drag on any member. Each follower draws its own outline\n(followDrag), moved by the same lattice delta as the leader's, and\nthe leader's events carry the set as `detail.icons`, itself first. The\ndefault action of the drop clamps the delta once for the whole group —\nthe arrangement is kept, and the group stops when its outermost member\nmeets the container's edge — then writes each member's origin plus that\ndelta. A cancel takes every outline down and writes nothing. A press on\nan unselected icon makes it the selection first (the outside listeners\nclear the others), so it drags alone; a Shift press that deselects the\npressed icon drags it alone too, the rest staying where they are.\n\nFor the selection to travel, a plain press on one of its members must not\ncollapse it: the other icons' outside-press listeners leave a selection\nalone when the press landed on an icon already selected in the same\nfield. A *click* on such an icon — released with no drag — collapses the\nselection to it on the release, so a click still single-selects and the\nsame press could have dragged them all.\n\nEscape is heard by a document listener scoped to the gesture rather than\nby the icon's own key handler, because a `movable`-only icon never takes\nfocus — an addition in the kit's idiom (a pointer-only gesture with no way\nout is the kind of gap SPEC §1 closes), not a claim about the Finder.\n\n`target` is the destination highlight the page sets on the folder under a\ndrag: the selected treatment of the art alone — inverted, or darkened\nunder `color` — with none of `selected`'s semantics: no event, no outside\nlistener, no `aria-selected`, the plate untouched.\n\nOpening gets the same treatment. The double-click is the pointer gesture,\nand its keyboard route is ⌘O / ⌘↓ — the System 7 Open shortcuts, with Ctrl\nstanding in for ⌘ off the Mac. Return is deliberately not one of them: the\nFinder's Return renamed, never opened, so on an editable icon it starts the\nedit and on a non-editable one it does nothing at all.\n\n### The name and the art are one target, and the second click decides\n\nA double-click opens the icon *wherever it lands* — the name is as much the\nicon as the picture is, and the Finder never made you aim at the 32 pixels of\nart. But the name is also where a single click renames, so the two gestures\nbegin with the same press and only the second one tells them apart.\n\nSo the rename waits for it. A press on the plate of an already-selected icon\narms the field rather than opening it, and the next press inside\nRENAME_DELAY_MS calls it off — leaving the double-click to open, with\nno rename box flashing up behind it. Nothing needs to *undo* an edit that\nbegan: the press that starts one and the press that opens are the same\npress, so the only thing that can be got right is not committing early.\n\nThe window is generous in the one direction that is cheap. Reading a lone\nclick as a pair costs a wait before the box appears; reading a pair as a lone\nclick renames when the user asked to open. The same reasoning covers a press\nthat turns into a drag, a press elsewhere, and any key — each calls the\npending rename off, because none of them is the click it is waiting for.\n\n### An icon alone is a picture; an icon in a field is an option\n\n`role=\"option\"` is only meaningful inside a `listbox` that owns it. Written\nunconditionally it is not merely untidy — the browser *drops* it, and\n`aria-selected` with it, so a `selectable` icon announced as a bare generic\nand its selection state reached assistive tech nowhere at all.\n\nSo the role follows the container. Owned, the icon is an `option` that names\nitself from its plate and publishes `aria-selected`. Unowned, it degrades to\n`role=\"img\"` with a name — the same vocabulary the derived open ghost uses,\nand true of what it is. Deliberately not `button`: that would promise Enter\nand Space activate, and here Return *renames* while the open route is ⌘O / ⌘↓.\n\nThe owner is `vf-icon-field`, the container a field of icons sits in, and\nit is what buys the selection state back:\n\n```html\n<vf-icon-field label=\"Desktop\">\n <vf-icon label=\"Macintosh HD\" selectable movable editable>…</vf-icon>\n <vf-icon label=\"Trash\" selectable movable editable>…</vf-icon>\n</vf-icon-field>\n```\n\nA `vf-desktop` cannot be that container itself: it also holds windows and a\nmenu bar, and a non-`option` child of a listbox is invalid the same way the\norphaned option was. The field is layout-neutral — placed icons anchor to\nthe nearest *positioned* ancestor, which is still the desktop's raster. An\nelement of your own carrying `role=\"listbox\"` still owns an icon the same\nway. One divergence from the APG listbox is deliberate: its options share\na single roving tab stop, while these stay one stop each, the way a Finder\nicon is reached on its own.\n\n**`selectable` is what makes an icon focusable**, and `movable`/`editable`\npresuppose it. That is the Finder's own model — you cannot move or rename\nwhat you have not selected — and the pointer path already assumed it: the\nrename opens on a press on the plate of an *already-selected* icon. A\n`movable`-only icon is a picture you can drag, not a widget.\n\n### The label is a property, because it is editable\n\nThe caption is `label` rather than slotted content: renaming means the\ncomponent owns the string and hands it back on `vf-change`, and it cannot own\ntext that lives in the consumer's DOM. An empty `label` draws no plate at\nall — that *is* the \"no label\" parameter, in preference to a second attribute\nthat could disagree with it. `editable` then lets a click on the plate of an\nalready-selected icon open the rename box a moment later, as the Finder's\ndoes (see above), with Return committing, Escape reverting, and the plate\nwidening as you type.\n\n### A name is never abbreviated, and never folded\n\nThere is no ellipsis, no clipping and no wrapping: one line, always. System 7\nsolved the long-name problem at the *other* end — HFS capped a filename at 31\ncharacters — so the Finder could afford to always draw the name in full, and\ndid. A name wider than its cell simply overflows it, centered, the way a name\nwider than a 32-pixel icon always did.\n\nStaying on one line is also what keeps the name on the grid: a single run has\na single measured width, so the even-plate rule above is one number rather\nthan one per line, each with its own parity. `width` is therefore the cell —\nthe grid pitch — not a bound on the name.\n\n`maxlength` (31) bounds the rename field rather than the `label` property:\nthe name belongs to the consumer's model, and truncating one handed to us\nwould lose data.\n\nThe box hugs its text while you type it, not only once you are done, which is\nwhat keeps the name from moving when the edit commits: the plate is the same\nwidth either side of it, so the glyphs stay exactly where they were.\n\nA file also has to be called *something*, so a rename committed empty — or\nas nothing but spaces — is refused rather than applied, and the previous name\ncomes back, which is what System 7 did. An empty `label` is still a perfectly\ngood state to *start* in, though: a freshly made icon has no name until it is\ngiven one. Such an icon draws no plate but stays selectable, focusable and\nrenameable — Return opens a field, and an edit with nothing in it yet is the\none time the box stops hugging and reserves a cell's width, since a field you\ncannot see is one you cannot type into.",
|
|
5208
|
+
"description": "`<vf-icon>` — a Finder icon: pixel art in a reserved cell with its name on a\nplate below, selectable, movable and renameable in place.\n\nIt is the one composite in the kit whose parts already existed separately —\n`vf-img` draws the art, `vf-list-item` inverts on selection, `vf-window`\ndrags — and the reason to have it anyway is that the *icon* is the unit the\nFinder actually manipulates. A picture and a caption that select together,\nmove together and rename together is a control, not a layout.\n\n### The art arrives by slot\n\nTwo slots, one per resource size — `large` is the 32×32 `ICN#` and `small`\nthe 16×16 `ics#` — each holding a `vf-img` around the consumer's own `<img>`:\n\n```html\n<vf-icon label=\"Macintosh HD\" selectable movable editable>\n <vf-img slot=\"large\"><img src=\"hd-32.png\" alt=\"\"></vf-img>\n <vf-img slot=\"small\"><img src=\"hd-16.png\" alt=\"\"></vf-img>\n</vf-icon>\n```\n\nSlots rather than `src` properties, for the reason `vf-img` exists at all:\nthe kit ships no raster files and never builds an `<img>` on a consumer's\nbehalf, so the graphic stays a real element in the light DOM with its own\n`alt`, `srcset`, loading behavior and asset URLs. A `src` string can express\nnone of that, and it could not hold an inline `<svg>` or a `<canvas>` either.\n`vf-list-item`'s `icon` slot makes the same trade.\n\nThe cost is that both files fetch even though one paints — pay it with two\ndata URIs, or slot only the size that view uses.\n\n### The cell is reserved, the art is registered in it\n\n`size` picks which slot paints *and* the cell it paints in: 32×32 or 16×16,\nheld whether or not there is art for it. A folder is 32×20 of ink and a\ndocument 25×32, and a row of them keeps one baseline only because the cell —\nnot the ink — is the unit, which is what an icon resource always was.\n\n### Everything centered lands on a whole pixel, by parity\n\nThe frame centers two things over one axis — the art cell and the name — and\na centered child sits at `(box − child) / 2`. That is a whole number exactly\nwhen the box and the child have the **same parity**. Half a system pixel is\nwhat fringes 1-bit art: the glyph stems smear across two device columns and\ngo gray, while the plate behind them stays sharp, because backgrounds are\npixel-snapped by the compositor and glyphs are not. A crisp plate under a\ngrey name is the signature of exactly this.\n\nSo the component makes the parities agree instead of correcting afterwards.\nThe cell is 32 or 16, and #measurePlate sizes the name plate to a\nwhole **even** number of system px — measured from the text, rounded up. Both\nchildren are then even, and so is every offset, at every display density and\nwhether or not a `width` is declared. Nothing is snapped, nothing leans on\nthe rasterizer, and `verify:icon` asserts both the geometry and that a field\nof names renders without a single gray pixel — with the kit's normal\nantialiasing left on, since a run on whole pixels gives it nothing to smooth.\n\nYour side of it is one rule: **a declared `width` must be even**, since the\ncomponent cannot round a number you chose. Supply art at the cell size too —\nwhich \"one image pixel is one system pixel\" already implies — because art\ndiffering from its cell by an odd number centers the same way.\n\n### Selection inverts, because the art is a 1-bit mask\n\nA System 7 icon is black ink and opaque white on a transparent surround —\nprecisely an image plus its mask — so inverting it flips ink and fill and\nleaves the surround alone, which is the whole of the classic selected\nappearance. `filter: invert(1)` reproduces it exactly for 1-bit art. Color\nart would invert into a photographic negative, which was never what\nSystem 7 showed: declare `color` and selection **darkens** the art\ninstead — Icon Utilities' ttSelected transform, every color blended\nhalfway toward black (`brightness(0.5)`), whites going gray, the\ntransparent surround still untouched. The declaration is the consumer's\nbecause the component cannot tell color art from a mask by looking at a\nslotted image. It covers whatever the cell shows, the derived open ghost\nincluded, so a color icon has one selected treatment open or closed. The\nlabel plate inverts to the `--vf-highlight` pair either way, sharing one\nselection color with `vf-list-item` — the selected name was inverted\nwhatever the art.\n\n### Open is derived, not shipped\n\nWith `open`, the art redraws as the Finder's open ghost: the outline held\nin solid black, the interior re-filled with the kit's loose 25% dither (the\nscrollbar trough's lattice), the transparent surround untouched. There is\nno second raster and no second fetch — the same alpha channel that makes\nselection an inversion makes the ghost derivable, and `src/open-art.ts`\nderives it from the slotted art by canvas compositing alone. No pixels are\never read back, so a cross-origin image that taints its canvas still works:\ntaint forbids reading, not drawing or displaying.\n\nThe ghost keeps the shape selection expects — ink and opaque white on a\ntransparent surround — so a selected open icon inverts exactly as a closed\none does, with no second treatment. The slot stays in the tree while the\nghost paints, hidden (it is where the art loads, and re-loads, from), and\nart the pipeline cannot draw — nothing slotted yet, a failed load, an\ninline `<svg>` — keeps rendering as itself rather than vanishing behind a\nstate it cannot show.\n\n### `movable`, not `draggable`\n\n`draggable` is a global HTML attribute *and* an `HTMLElement` accessor, so\ndeclaring it would both shadow a platform member and hand the element to the\nbrowser's own drag-and-drop machinery. This is the `align` trap from\n`vf-stack` in a second costume — `align`, `hidden`, `dir`, `draggable` and\n`title` all carry behavior a custom element never asked for. The kit already\nspells this parameter `movable` on `vf-window`, so the icon does too, and it\nmoves the same way: `DragController` tracks the gesture and\n`PlacementController` writes the result into `left`/`top` in whole system px,\nthe art's own unit — the same pair markup places an icon with, so a moved\nicon is still where it was dropped after a zoom.\n\nDragging is a pointer gesture with no keyboard equivalent, which is the kind\nof gap the kit closes rather than inherits (SPEC §1): a focused movable icon\nalso moves under the arrow keys, one system px at a time and eight with\nShift. Focus is what `selectable` grants, so the keyboard half of `movable`\nand `editable` presupposes it — see the role section below.\n\n### The drag is an outline; the icon stays put\n\nThe Finder dragged an icon as a dotted outline — the mask's boundary and\nthe name's rectangle — drawn over everything with the XOR pen, the icon\nitself staying put until the drop. The kit draws it from what it already\nhas: deriveDragOutline finds the ring the way the open ghost does\n(the silhouette from the art's alpha, the plate's rectangle united with\nit, the whole inset by one pixel as `FrameRgn` inset a region, the ring\nthe inset removes — so where the art and the plate abut, the outline\nwraps around both), and dotOutline dots it against a 2×2 checker\n— compositing only, so cross-origin art derives too, and art the\npipeline cannot draw falls back to the cell's rectangle. The canvas\ncovers the frame widened to the plate, since a name wider than a\ndeclared `width` overhangs the frame.\nThe canvas paints with the cursor's XOR recipe, `filter: invert(1)` under\n`mix-blend-mode: difference`: a dotted black line over a white window\nbody, and over the desktop dither a black line, the composition\nQuickDraw's pattern pen gave — the checker's phase is taken from where\nthe outline lands on the *screen* (penPhase), so its dots fall on\nthe pixels the dither leaves white wherever it goes, and the dither's own\nink fills in between them.\n\nIt draws on the desktop's drag surface: a layer in the screen's shadow,\nover windows, palettes and the menu bar alike, clipped at the raster's\nedge like everything else, reached through the `vf-drag-surface-request`\nhandshake (bubbles, non-composed — it travels up the slots the icon is\nassigned to, so it works from inside a window body). With no desktop the\ncanvas lives in the icon's own frame instead, translated by the delta,\nclipped by whatever clips the icon; the gesture and its events are the\nsame either way. Its origin is a whole count of system px from the\nsurface's, written as a live `sysLength()`, so a scale change mid-gesture\ncannot smear it.\n\n### The drag reports, the page files\n\nThe kit ships no filing semantics — folders are the page's own catalog —\nso a drag is a stream of events and a cancelable commit, on the icon:\n\n- `vf-drag-start` `{ left, top }` on the first lattice step away from the\n press (a plain click never starts one, and never fires a drop);\n- `vf-drag` `{ clientX, clientY, left, top, x, y }` on every step that\n changed the snapped proposal;\n- `vf-drop`, the same shape at the release, **cancelable**: its default\n action is the move, the proposal written through `left`/`top` and\n clamped whole in the container measured at the press — held at the\n origin only in a scrolling plane, whose rails reach the rest. `preventDefault()`\n writes nothing — the page re-parents or places the icon itself, the\n outline's origin converted with the destination's `placementAt`;\n- `vf-drag-cancel` `{}` on Escape or a `pointercancel`: nothing written.\n\n`left`/`top` in the detail are the *proposal*, unclamped — the origin plus\nthe lattice-snapped delta — and only the default action clamps, so a page\nthat cancels gets the raw gesture. `x`/`y` are the outline's top-left in\nviewport CSS px: the frame's box at the press translated by the delta,\nthe press offset already inside it, so an icon placed at the\ndestination's `placementAt(x, y)` lands exactly where the outline was.\n\n### The selection travels\n\nA drag that begins on a *selected* icon takes every other selected,\nmovable icon of its field with it — the Finder outlined the whole\nselection under a drag on any member. Each follower draws its own outline\n(followDrag), moved by the same lattice delta as the leader's, and\nthe leader's events carry the set as `detail.icons`, itself first. The\ndefault action of the drop clamps the delta once for the whole group —\nthe arrangement is kept, and the group stops when its outermost member\nmeets the container's edge — then writes each member's origin plus that\ndelta. A cancel takes every outline down and writes nothing. A press on\nan unselected icon makes it the selection first (the outside listeners\nclear the others), so it drags alone; a Shift press that deselects the\npressed icon drags it alone too, the rest staying where they are.\n\nFor the selection to travel, a plain press on one of its members must not\ncollapse it: the other icons' outside-press listeners leave a selection\nalone when the press landed on an icon already selected in the same\nfield. A *click* on such an icon — released with no drag — collapses the\nselection to it on the release, so a click still single-selects and the\nsame press could have dragged them all.\n\nEscape is heard by a document listener scoped to the gesture rather than\nby the icon's own key handler, because a `movable`-only icon never takes\nfocus — an addition in the kit's idiom (a pointer-only gesture with no way\nout is the kind of gap SPEC §1 closes), not a claim about the Finder.\n\n`target` is the destination highlight the page sets on the folder under a\ndrag: the selected treatment of the art alone — inverted, or darkened\nunder `color` — with none of `selected`'s semantics: no event, no outside\nlistener, no `aria-selected`, the plate untouched.\n\n### The page drags it too\n\nThe Finder's Clean Up walked a container's icons one at a time, each\nicon's outline travelling from where it sat to its cell and the icon\nlanding when the outline arrived — the drag's own vocabulary, with the\nFinder for a hand. dragTo is that move for one icon: the outline\nbuilt and placed as a drag's is, on the desktop's surface or in the frame,\nstepped to the landing at the walk's cadence (runOutlineTravel,\nsrc/motion.ts — the kit's, no knobs), then the outline down and the icon\nwritten where a drop would write it. The landing is resolved before the\noutline sets off, so the outline goes where the icon will land rather\nthan where a pair past the container's edge pointed. moveTo is\nthat write alone: the drop's clamp and snap, one `vf-placement-change` —\nwhere a property set is the authored pair, unclamped. Neither fires a\ndrag event (the page is the mover and already knows) and neither needs\n`movable`, which says the hand may drag: the page may always place its\nicon. `vf-icon-field.dragIcons` walks a set through `dragTo` with the\nFinder's beat between landings.\n\nA walk is finished, never cancelled: a press anywhere, or Escape, lands\nthe icon at once — the user wants the machine back, and the move was the\npage's to make. A second `dragTo`, or a `moveTo`, finishes the one in\nflight the same way; leaving the DOM takes the outline down and writes\nnothing. Under `prefers-reduced-motion` the icon lands at once with no\noutline, the blink's posture — as does one being renamed, or one under\nthe hand's own press.\n\nOpening gets the same treatment. The double-click is the pointer gesture,\nand its keyboard route is ⌘O / ⌘↓ — the System 7 Open shortcuts, with Ctrl\nstanding in for ⌘ off the Mac. Return is deliberately not one of them: the\nFinder's Return renamed, never opened, so on an editable icon it starts the\nedit and on a non-editable one it does nothing at all.\n\n### The name and the art are one target, and the second click decides\n\nA double-click opens the icon *wherever it lands* — the name is as much the\nicon as the picture is, and the Finder never made you aim at the 32 pixels of\nart. But the name is also where a single click renames, so the two gestures\nbegin with the same press and only the second one tells them apart.\n\nSo the rename waits for it. A press on the plate of an already-selected icon\narms the field rather than opening it, and the next press inside\nRENAME_DELAY_MS calls it off — leaving the double-click to open, with\nno rename box flashing up behind it. Nothing needs to *undo* an edit that\nbegan: the press that starts one and the press that opens are the same\npress, so the only thing that can be got right is not committing early.\n\nThe window is generous in the one direction that is cheap. Reading a lone\nclick as a pair costs a wait before the box appears; reading a pair as a lone\nclick renames when the user asked to open. The same reasoning covers a press\nthat turns into a drag, a press elsewhere, and any key — each calls the\npending rename off, because none of them is the click it is waiting for.\n\n### An icon alone is a picture; an icon in a field is an option\n\n`role=\"option\"` is only meaningful inside a `listbox` that owns it. Written\nunconditionally it is not merely untidy — the browser *drops* it, and\n`aria-selected` with it, so a `selectable` icon announced as a bare generic\nand its selection state reached assistive tech nowhere at all.\n\nSo the role follows the container. Owned, the icon is an `option` that names\nitself from its plate and publishes `aria-selected`. Unowned, it degrades to\n`role=\"img\"` with a name — the same vocabulary the derived open ghost uses,\nand true of what it is. Deliberately not `button`: that would promise Enter\nand Space activate, and here Return *renames* while the open route is ⌘O / ⌘↓.\n\nThe owner is `vf-icon-field`, the container a field of icons sits in, and\nit is what buys the selection state back:\n\n```html\n<vf-icon-field label=\"Desktop\">\n <vf-icon label=\"Macintosh HD\" selectable movable editable>…</vf-icon>\n <vf-icon label=\"Trash\" selectable movable editable>…</vf-icon>\n</vf-icon-field>\n```\n\nA `vf-desktop` cannot be that container itself: it also holds windows and a\nmenu bar, and a non-`option` child of a listbox is invalid the same way the\norphaned option was. The field is layout-neutral — placed icons anchor to\nthe nearest *positioned* ancestor, which is still the desktop's raster. An\nelement of your own carrying `role=\"listbox\"` still owns an icon the same\nway. One divergence from the APG listbox is deliberate: its options share\na single roving tab stop, while these stay one stop each, the way a Finder\nicon is reached on its own.\n\n**`selectable` is what makes an icon focusable**, and `movable`/`editable`\npresuppose it. That is the Finder's own model — you cannot move or rename\nwhat you have not selected — and the pointer path already assumed it: the\nrename opens on a press on the plate of an *already-selected* icon. A\n`movable`-only icon is a picture you can drag, not a widget.\n\n### The label is a property, because it is editable\n\nThe caption is `label` rather than slotted content: renaming means the\ncomponent owns the string and hands it back on `vf-change`, and it cannot own\ntext that lives in the consumer's DOM. An empty `label` draws no plate at\nall — that *is* the \"no label\" parameter, in preference to a second attribute\nthat could disagree with it. `editable` then lets a click on the plate of an\nalready-selected icon open the rename box a moment later, as the Finder's\ndoes (see above), with Return committing, Escape reverting, and the plate\nwidening as you type.\n\n### A name is never abbreviated, and never folded\n\nThere is no ellipsis, no clipping and no wrapping: one line, always. System 7\nsolved the long-name problem at the *other* end — HFS capped a filename at 31\ncharacters — so the Finder could afford to always draw the name in full, and\ndid. A name wider than its cell simply overflows it, centered, the way a name\nwider than a 32-pixel icon always did.\n\nStaying on one line is also what keeps the name on the grid: a single run has\na single measured width, so the even-plate rule above is one number rather\nthan one per line, each with its own parity. `width` is therefore the cell —\nthe grid pitch — not a bound on the name.\n\n`maxlength` (31) bounds the rename field rather than the `label` property:\nthe name belongs to the consumer's model, and truncating one handed to us\nwould lose data.\n\nThe box hugs its text while you type it, not only once you are done, which is\nwhat keeps the name from moving when the edit commits: the plate is the same\nwidth either side of it, so the glyphs stay exactly where they were.\n\nA file also has to be called *something*, so a rename committed empty — or\nas nothing but spaces — is refused rather than applied, and the previous name\ncomes back, which is what System 7 did. An empty `label` is still a perfectly\ngood state to *start* in, though: a freshly made icon has no name until it is\ngiven one. Such an icon draws no plate but stays selectable, focusable and\nrenameable — Return opens a field, and an edit with nothing in it yet is the\none time the box stops hugging and reserves a cell's width, since a field you\ncannot see is one you cannot type into.",
|
|
5114
5209
|
"name": "VfIcon",
|
|
5115
5210
|
"cssProperties": [
|
|
5116
5211
|
{
|
|
5117
|
-
"description": "Space between the art cell and the name plate",
|
|
5212
|
+
"description": "Space between the art cell and the name plate; 0 by default, the plate directly under the cell as the Finder drew it",
|
|
5118
5213
|
"name": "--vf-icon-gap"
|
|
5119
5214
|
},
|
|
5120
5215
|
{
|
|
@@ -5435,7 +5530,18 @@
|
|
|
5435
5530
|
"kind": "field",
|
|
5436
5531
|
"name": "#keepWhole",
|
|
5437
5532
|
"privacy": "private",
|
|
5438
|
-
"description": "Where a moved icon is allowed to end up, in system px. Unlike `vf-window`,\nwhich only keeps a grabbable strip on screen, an icon is small enough to\nhold whole — losing half of one to an edge reads as a bug rather than as a\nwindow pushed aside."
|
|
5533
|
+
"description": "Where a moved icon is allowed to end up, in system px. Unlike `vf-window`,\nwhich only keeps a grabbable strip on screen, an icon is small enough to\nhold whole — losing half of one to an edge reads as a bug rather than as a\nwindow pushed aside. Except in a box that scrolls (#anchorScrolls):\nthe rails reach anything placed past such a box, so only its origin holds\n— the Finder put an icon below a window's fold and scrolled to it."
|
|
5534
|
+
},
|
|
5535
|
+
{
|
|
5536
|
+
"kind": "method",
|
|
5537
|
+
"name": "#anchorScrolls",
|
|
5538
|
+
"privacy": "private",
|
|
5539
|
+
"return": {
|
|
5540
|
+
"type": {
|
|
5541
|
+
"text": "boolean"
|
|
5542
|
+
}
|
|
5543
|
+
},
|
|
5544
|
+
"description": "Whether the box this icon is placed in scrolls: a `vf-scroll-area`'s\nplane, which a window body is under `scrollbars`. Found by walking the\nflat tree from the icon toward its positioning parent — through the\nslots it is assigned to and out of the shadow roots on the way. A scroll\narea met before the parent is the one whose plane the icon sits on; one\nmet only beyond it (a placed container inside a scrolling window) is not\nthis icon's box. Read per clamp: a handful of nodes, and the answer\nchanges when the icon is re-parented."
|
|
5439
5545
|
},
|
|
5440
5546
|
{
|
|
5441
5547
|
"kind": "field",
|
|
@@ -5476,6 +5582,34 @@
|
|
|
5476
5582
|
"name": "#onDragKeyDown",
|
|
5477
5583
|
"privacy": "private"
|
|
5478
5584
|
},
|
|
5585
|
+
{
|
|
5586
|
+
"kind": "field",
|
|
5587
|
+
"name": "#travel",
|
|
5588
|
+
"privacy": "private",
|
|
5589
|
+
"type": {
|
|
5590
|
+
"text": "TravelHandle | null"
|
|
5591
|
+
},
|
|
5592
|
+
"default": "null",
|
|
5593
|
+
"description": "The walk in flight — a page's dragTo — or null."
|
|
5594
|
+
},
|
|
5595
|
+
{
|
|
5596
|
+
"kind": "field",
|
|
5597
|
+
"name": "#interrupt",
|
|
5598
|
+
"privacy": "private",
|
|
5599
|
+
"readonly": true,
|
|
5600
|
+
"default": "new DocumentListenersController(this, () => [ [document, 'pointerdown', this.#onTravelPress, true], [document, 'keydown', this.#onTravelKeyDown, true], ])",
|
|
5601
|
+
"description": "A press anywhere, or Escape, finishes a walk in flight: the icon lands\nat once. Scoped to the travel, on the document, capture phase, the key\nstopped there — the drag's Escape rule above, for the same reasons."
|
|
5602
|
+
},
|
|
5603
|
+
{
|
|
5604
|
+
"kind": "field",
|
|
5605
|
+
"name": "#onTravelPress",
|
|
5606
|
+
"privacy": "private"
|
|
5607
|
+
},
|
|
5608
|
+
{
|
|
5609
|
+
"kind": "field",
|
|
5610
|
+
"name": "#onTravelKeyDown",
|
|
5611
|
+
"privacy": "private"
|
|
5612
|
+
},
|
|
5479
5613
|
{
|
|
5480
5614
|
"kind": "method",
|
|
5481
5615
|
"name": "#frameRect",
|
|
@@ -5633,7 +5767,7 @@
|
|
|
5633
5767
|
}
|
|
5634
5768
|
}
|
|
5635
5769
|
],
|
|
5636
|
-
"description": "The drop's default action. Alone, the proposal clamped whole by\n#keepWhole. As a group, one delta for all: clamped so that the\nmember nearest each edge stays whole — the group stops when its\noutermost icon meets the container, and the arrangement is kept — then\neach member's origin plus that delta. A group no edge can hold (a member\nwider than the container) is held off the near edge only, the\nsingle-icon rule."
|
|
5770
|
+
"description": "The drop's default action. Alone, the proposal clamped whole by\n#keepWhole. As a group, one delta for all: clamped so that the\nmember nearest each edge stays whole — the group stops when its\noutermost icon meets the container, and the arrangement is kept — then\neach member's origin plus that delta. A group no edge can hold (a member\nwider than the container) is held off the near edge only, the\nsingle-icon rule; so is any group in a box that scrolls, where only the\norigin holds (#anchorScrolls)."
|
|
5637
5771
|
},
|
|
5638
5772
|
{
|
|
5639
5773
|
"kind": "method",
|
|
@@ -5669,7 +5803,7 @@
|
|
|
5669
5803
|
}
|
|
5670
5804
|
}
|
|
5671
5805
|
],
|
|
5672
|
-
"description": "Build this icon's outline for a drag, and put it where it draws: the\ndesktop's drag surface when the leader's handshake found one, else the\nicon's own frame. The geometry is measured off the rendered parts —\nthe frame (`at`, its box at the press), the art as it paints (the ghost\nwhile `open`), the cell and the plate — in system px relative to the\
|
|
5806
|
+
"description": "Build this icon's outline for a drag, and put it where it draws: the\ndesktop's drag surface when the leader's handshake found one, else the\nicon's own frame. The geometry is measured off the rendered parts —\nthe frame (`at`, its box at the press), the art as it paints (the ghost\nwhile `open`), the cell and the plate — in system px relative to the\noutline's box, the frame widened to a plate that overhangs it, so the\nring lands on the pixels the frame paints. Null when there is nothing\nto draw with."
|
|
5673
5807
|
},
|
|
5674
5808
|
{
|
|
5675
5809
|
"kind": "method",
|
|
@@ -5942,6 +6076,54 @@
|
|
|
5942
6076
|
],
|
|
5943
6077
|
"description": "Whether a viewport box (CSS px) touches the icon the way the Finder's\nselection rectangle asked: its art cell or its name plate. The empty\ncell around narrower art and the frame's margin beside the plate count\nfor nothing — a rectangle dragged through the gap between two icons\nselects neither. `vf-icon-field`'s rubber band reads it."
|
|
5944
6078
|
},
|
|
6079
|
+
{
|
|
6080
|
+
"kind": "method",
|
|
6081
|
+
"name": "moveTo",
|
|
6082
|
+
"return": {
|
|
6083
|
+
"type": {
|
|
6084
|
+
"text": "void"
|
|
6085
|
+
}
|
|
6086
|
+
},
|
|
6087
|
+
"parameters": [
|
|
6088
|
+
{
|
|
6089
|
+
"name": "left",
|
|
6090
|
+
"type": {
|
|
6091
|
+
"text": "number"
|
|
6092
|
+
}
|
|
6093
|
+
},
|
|
6094
|
+
{
|
|
6095
|
+
"name": "top",
|
|
6096
|
+
"type": {
|
|
6097
|
+
"text": "number"
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
],
|
|
6101
|
+
"description": "Place the icon as a drop would: the stated pair — the origin point\nunder an `origin`, the corner without one — clamped whole in the\ncontainer measured now (held at the origin only in a scrolling plane),\nsnapped onto the lattice, announced as one `vf-placement-change`. A\n`left`/`top` write is the authored pair, unclamped; this is the\ngesture's. Finishes a walk in flight first."
|
|
6102
|
+
},
|
|
6103
|
+
{
|
|
6104
|
+
"kind": "method",
|
|
6105
|
+
"name": "dragTo",
|
|
6106
|
+
"return": {
|
|
6107
|
+
"type": {
|
|
6108
|
+
"text": "Promise<boolean>"
|
|
6109
|
+
}
|
|
6110
|
+
},
|
|
6111
|
+
"parameters": [
|
|
6112
|
+
{
|
|
6113
|
+
"name": "left",
|
|
6114
|
+
"type": {
|
|
6115
|
+
"text": "number"
|
|
6116
|
+
}
|
|
6117
|
+
},
|
|
6118
|
+
{
|
|
6119
|
+
"name": "top",
|
|
6120
|
+
"type": {
|
|
6121
|
+
"text": "number"
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
6124
|
+
],
|
|
6125
|
+
"description": "Move the icon the way a drag moves it (see the class doc): its outline\ntravels from where it sits to where it will land, and the icon lands\nwhen the outline arrives, written as moveTo writes. Resolves\nonce it has landed — to whether the outline travelled; false when the\nicon landed at once: already there, `prefers-reduced-motion`, nothing\nto draw the outline with, or not in the DOM (nothing written then).\nFires no drag event. A walk already in flight here is finished first."
|
|
6126
|
+
},
|
|
5945
6127
|
{
|
|
5946
6128
|
"kind": "field",
|
|
5947
6129
|
"name": "#onPointerDown",
|
|
@@ -23357,6 +23539,30 @@
|
|
|
23357
23539
|
],
|
|
23358
23540
|
"description": "Place the host at a system-px origin, clamped by its own rule and snapped.\n\nThe clamp runs *here*, during the gesture, which is the moment the user is\nactually pushing against an edge — and nowhere else. Re-clamping later, on\na parent that shrank under a zoom, would move a host nobody moved and would\nnot give the position back when the parent grew again.\n\nIt runs against the box seed measured, not a fresh one. The\ndifference only shows when the movable contract is unmet: a host with no\nstated origin is in normal flow, so its first move takes it out — and an\nauto-height parent then collapses to whatever is left, which is a box the\nhost never actually sat in. Re-measuring per move would clamp the rest of\nthe gesture into that phantom, walking the host to the parent's origin\nwhile the user drags away from it. The box at the moment of the press is\nthe one the user is pushing against.\n\nThe pair is the origin point; the clamp is a rule about the *box*. So\nthe origin's offset comes off before the clamp and goes back on after,\nand a dragged title stays centered on wherever it was dropped."
|
|
23359
23541
|
},
|
|
23542
|
+
{
|
|
23543
|
+
"kind": "method",
|
|
23544
|
+
"name": "resolve",
|
|
23545
|
+
"return": {
|
|
23546
|
+
"type": {
|
|
23547
|
+
"text": "{ x: number; y: number }"
|
|
23548
|
+
}
|
|
23549
|
+
},
|
|
23550
|
+
"parameters": [
|
|
23551
|
+
{
|
|
23552
|
+
"name": "x",
|
|
23553
|
+
"type": {
|
|
23554
|
+
"text": "number"
|
|
23555
|
+
}
|
|
23556
|
+
},
|
|
23557
|
+
{
|
|
23558
|
+
"name": "y",
|
|
23559
|
+
"type": {
|
|
23560
|
+
"text": "number"
|
|
23561
|
+
}
|
|
23562
|
+
}
|
|
23563
|
+
],
|
|
23564
|
+
"description": "The pair moveTo would write for a requested origin — clamped by\nthe host's own rule against the box seed measured and snapped\nonto the lattice — without writing it. A move that shows where it is\ngoing before it goes (`vf-icon.dragTo`) reads its landing here first."
|
|
23565
|
+
},
|
|
23360
23566
|
{
|
|
23361
23567
|
"kind": "method",
|
|
23362
23568
|
"name": "#measureBounds",
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { LitElement, type PropertyValues } from 'lit';
|
|
2
|
-
import type { VfIconSize } from './vf-icon.js';
|
|
2
|
+
import type { VfIcon, VfIconSize } from './vf-icon.js';
|
|
3
|
+
/** One move of a walk ({@link VfIconField.dragIcons}): the icon and the pair it goes to. */
|
|
4
|
+
export interface VfIconMove {
|
|
5
|
+
icon: VfIcon;
|
|
6
|
+
left: number;
|
|
7
|
+
top: number;
|
|
8
|
+
}
|
|
3
9
|
declare const VfIconField_base: (new (...args: any[]) => import("../size.js").VfSizedInterface) & (new (...args: any[]) => import("../position.js").VfPositionedInterface) & typeof LitElement;
|
|
4
10
|
/**
|
|
5
11
|
* `<vf-icon-field>` — the container a field of `vf-icon`s sits in: a desktop's
|
|
@@ -95,6 +101,21 @@ declare const VfIconField_base: (new (...args: any[]) => import("../size.js").Vf
|
|
|
95
101
|
* background pans a scrolling window as it always did; the band is a mouse
|
|
96
102
|
* and pen gesture.
|
|
97
103
|
*
|
|
104
|
+
* ### The walk
|
|
105
|
+
*
|
|
106
|
+
* The Finder's Clean Up moved a container's icons one at a time, each
|
|
107
|
+
* icon's dotted outline travelling to its cell and the icon landing when
|
|
108
|
+
* the outline arrived. {@link dragIcons} is that walk: `{ icon, left, top }`
|
|
109
|
+
* moves in the page's order — which icon goes first is a statement about
|
|
110
|
+
* the page's lattice, and the kit ships none — each through the icon's own
|
|
111
|
+
* `dragTo`, with a beat (`WALK_BEAT_MS`, src/motion.ts) after every landing
|
|
112
|
+
* that showed. It resolves when the last icon has landed. One walk per
|
|
113
|
+
* field: a second call finishes the first. A press anywhere, or Escape,
|
|
114
|
+
* finishes it too — every icon still to go lands at its target at once, the
|
|
115
|
+
* Finder never made you wait — and so does the field leaving the DOM; an
|
|
116
|
+
* icon gone from the DOM by its turn is skipped. Under
|
|
117
|
+
* `prefers-reduced-motion` every icon lands at once, no outline, no beat.
|
|
118
|
+
*
|
|
98
119
|
* ### `size` is the view's
|
|
99
120
|
*
|
|
100
121
|
* `large` or `small`, written onto every icon in the field when set and
|
|
@@ -137,6 +158,13 @@ export declare class VfIconField extends VfIconField_base {
|
|
|
137
158
|
constructor();
|
|
138
159
|
disconnectedCallback(): void;
|
|
139
160
|
protected updated(changed: PropertyValues<this>): void;
|
|
161
|
+
/**
|
|
162
|
+
* Walk the icons to their targets one at a time, in the order given (see
|
|
163
|
+
* the class doc): each through its own {@link VfIcon.dragTo}, the beat
|
|
164
|
+
* after every landing that showed. Resolves when the last has landed. A
|
|
165
|
+
* walk already in flight is finished first.
|
|
166
|
+
*/
|
|
167
|
+
dragIcons(moves: readonly VfIconMove[]): Promise<void>;
|
|
140
168
|
protected render(): import("lit-html").TemplateResult<1>;
|
|
141
169
|
}
|
|
142
170
|
declare global {
|