@gjermundgaraba-designs/pixel-paper 0.20.1 → 0.22.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.
Files changed (74) hide show
  1. package/DESIGN.md +21 -15
  2. package/dist/src/bench/bench.d.ts +14 -0
  3. package/dist/src/bench/bench.d.ts.map +1 -0
  4. package/dist/src/bench/bench.js +9 -0
  5. package/dist/src/bench/index.d.ts +3 -0
  6. package/dist/src/bench/index.d.ts.map +1 -0
  7. package/dist/src/bench/index.js +1 -0
  8. package/dist/src/chip/chip.d.ts +10 -0
  9. package/dist/src/chip/chip.d.ts.map +1 -1
  10. package/dist/src/chip/chip.js +19 -0
  11. package/dist/src/chip/index.d.ts +2 -2
  12. package/dist/src/chip/index.d.ts.map +1 -1
  13. package/dist/src/chip/index.js +1 -1
  14. package/dist/src/drag/drag.d.ts +18 -0
  15. package/dist/src/drag/drag.d.ts.map +1 -1
  16. package/dist/src/drag/drag.js +35 -7
  17. package/dist/src/field/field.d.ts +3 -0
  18. package/dist/src/field/field.d.ts.map +1 -1
  19. package/dist/src/field/field.js +2 -2
  20. package/dist/src/index.d.ts +1 -0
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +1 -0
  23. package/dist/src/lineDiff/index.d.ts +2 -0
  24. package/dist/src/lineDiff/index.d.ts.map +1 -1
  25. package/dist/src/lineDiff/index.js +1 -0
  26. package/dist/src/lineDiff/lineDiff.d.ts +8 -4
  27. package/dist/src/lineDiff/lineDiff.d.ts.map +1 -1
  28. package/dist/src/lineDiff/lineDiff.js +19 -15
  29. package/dist/src/lineDiff/patch.d.ts +35 -0
  30. package/dist/src/lineDiff/patch.d.ts.map +1 -0
  31. package/dist/src/lineDiff/patch.js +146 -0
  32. package/dist/src/menu/menu.d.ts +6 -6
  33. package/dist/src/rail/index.d.ts +1 -1
  34. package/dist/src/rail/index.d.ts.map +1 -1
  35. package/dist/src/rail/index.js +1 -1
  36. package/dist/src/rail/rail.d.ts +217 -17
  37. package/dist/src/rail/rail.d.ts.map +1 -1
  38. package/dist/src/rail/rail.js +133 -15
  39. package/dist/src/reel/index.d.ts +1 -1
  40. package/dist/src/reel/index.d.ts.map +1 -1
  41. package/dist/src/reel/index.js +1 -1
  42. package/dist/src/reel/layout.d.ts +21 -26
  43. package/dist/src/reel/layout.d.ts.map +1 -1
  44. package/dist/src/reel/layout.js +84 -106
  45. package/dist/src/reel/reel.d.ts +30 -172
  46. package/dist/src/reel/reel.d.ts.map +1 -1
  47. package/dist/src/reel/reel.js +144 -195
  48. package/dist/src/rows/rows.d.ts +9 -1
  49. package/dist/src/rows/rows.d.ts.map +1 -1
  50. package/dist/src/rows/rows.js +1 -1
  51. package/dist/src/sortable/sortable.d.ts +30 -30
  52. package/dist/src/toast/toast.d.ts +12 -12
  53. package/dist/src/window/window.d.ts +3 -1
  54. package/dist/src/window/window.d.ts.map +1 -1
  55. package/package.json +1 -1
  56. package/src/bench/bench.ts +24 -0
  57. package/src/bench/index.ts +2 -0
  58. package/src/chip/chip.ts +31 -0
  59. package/src/chip/index.ts +2 -2
  60. package/src/drag/drag.ts +60 -1
  61. package/src/field/field.ts +5 -1
  62. package/src/index.ts +1 -0
  63. package/src/lineDiff/index.ts +2 -0
  64. package/src/lineDiff/lineDiff.ts +29 -24
  65. package/src/lineDiff/patch.ts +218 -0
  66. package/src/pixel-paper.css +202 -32
  67. package/src/rail/index.ts +2 -0
  68. package/src/rail/rail.ts +181 -16
  69. package/src/reel/index.ts +0 -1
  70. package/src/reel/layout.ts +78 -118
  71. package/src/reel/reel.ts +108 -216
  72. package/src/rows/rows.ts +14 -2
  73. package/src/tokens.css +2 -1
  74. package/src/window/window.ts +3 -1
package/DESIGN.md CHANGED
@@ -207,6 +207,7 @@ spacing:
207
207
  deck-bottom: "80px"
208
208
  deck-max: "1160px"
209
209
  rail: "272px"
210
+ rail-drawer: "320px"
210
211
  card-max: "680px"
211
212
  card-narrow: "440px"
212
213
  row-min: "34px"
@@ -917,11 +918,13 @@ The Deck is one centred column, `max-width: 1160px` (`--deck`), padded 24px top,
917
918
 
918
919
  **Toasts:** the stack sits 16px in from the bottom right, `min(360px, 100vw - 32px)` wide, newest lowest, 10px apart, standing 12px clear of a showing Banner (anchored to it); on a phone it spans the bottom. While the system's Dialog is open the stack moves inside it, so a toast raised from a card prints above the card and can be used; a raw `<dialog>` an adopter opens with `showModal()` is not followed.
919
920
 
920
- **Reel:** a row of columns of windows that fills whatever holds it and scrolls sideways when the columns are wider than it, and down where a column's panes need more than its height, `--reel-gutter` (16px) between columns, between a column's panes and at either end. A column's width is a share of the reel's (`width × (view − gutter) − gutter`, so columns whose shares add up to one fill it exactly), never under `--reel-column-min` (280px); a column is the reel's height and a pane's height its share of its column by weight, never under `--reel-pane-min` (120px), a column of more panes than fit growing taller. The shares a width steps through (a third, half, two thirds, the whole) are the component's, in its code, not tokens: they are the words it says as much as the sizes it prints.
921
+ **Reel:** a row of columns of windows that fills whatever holds it and scrolls sideways when the columns are wider than it, and down where a column's panes need more than its height, `--reel-gutter` (16px) between columns, between a column's panes and at all four edges (with no column head, the gutter above the panes is the reel's own). A column's width is a share of the reel's (`width × (view − gutter) − gutter`, so columns whose shares add up to one fill it exactly), never under `--reel-column-min` (280px); a column is the reel's height less the gutters above and below it, and a pane's height its share of its column by weight, never under `--reel-pane-min` (120px), a column of more panes than fit growing taller. The shares a width steps through (a third, half, two thirds, the whole) are the component's, in its code, not tokens: they are the words it says as much as the sizes it prints.
921
922
 
922
- **Rail:** a column of panels beside the deck (`--rail`, 272px, then the deck centred in what is left), fixed from the top bar's lower edge (hung from it by anchor positioning, like the stage) to the window's bottom, scrolling itself, padded `24px 10px 24px 20px` so its panels' shadows print. At 900px and under it leaves the page: the deck starts with a small button (the `sidebar` glyph and the rail's name) that opens the same element as a drawer.
923
+ **Rail:** a column of panels beside the deck (`--rail`, 272px, then the deck centred in what is left), fixed from the top bar's lower edge (hung from it by anchor positioning, like the stage) to the window's bottom, scrolling itself, padded `24px 10px 24px 20px` so its panels' shadows print. The reader sizes it from its right edge, between 220px (the least its panels still fit: a tree's row keeps its glyph, a dot and a few words of its title) and 480px (the most, which leaves the deck at least 421px of a 901px window, the narrowest the rail stands in, more than a phone has); the width they give is set as `--rail` on `.railed`, so the page's padding and the rail's width move together, and a bench beside it follows. At 900px and under it leaves the page: the deck starts with a small button (the `sidebar` glyph and the rail's name) that opens the same element as a drawer, `--rail-drawer` (320px) wide whatever width the reader gave the rail, since a width chosen beside a page is no measure of a drawer over a phone.
923
924
 
924
- Frames with no surface of their own (Deck, Wall, Stage) are described here rather than among the components.
925
+ Frames with no surface of their own (Deck, Wall, Stage, Bench) are described here rather than among the components.
926
+
927
+ **Bench:** a page whose work is the window: a task's terminals and editors side by side, which a reading column would cramp and a frame of its own would scroll twice. Fixed from the top bar's lower edge (hung from it by anchor positioning, like the stage and the rail) to the status bar, the window wide, or from the rail's edge beside a rail (a fixed box takes nothing from `.railed`'s padding, so the bench reads `--rail` itself and follows the reader's width), and from the left edge at 900px and under, where the rail is a drawer. It is a grid of two rows: the head (a PageHead, notices, the rail's open button first) at its own height, padded `24px 16px 0` with 24px between its parts, and the work in all the height left (`minmax(0, 1fr)`), where a reel takes 100% of it. The head's 16px is a reel's gutter (`--reel-gutter`), so the head's edge lines up with the windows' and the reel's own top gutter is the space between them. The page does not scroll: the work scrolls itself where it must (a reel's columns sideways, and down where a column is taller). The Deck stays the frame for pages that read; the Stage, for a canvas with no head.
925
928
 
926
929
  **Stage:** the ground itself for a canvas: fixed from the top bar's lower edge (it hangs from the bar by anchor positioning, so a phone's two-line bar fits) to the window's edges. It does not scroll; what is on it pans. Its floating tools sit 16px in from the left edge, centred (along the bottom, centred, on a phone); its bar sits 16px down from the top, centred, wrapping when narrow.
927
930
 
@@ -931,7 +934,7 @@ Breakpoints: 600px (phone; the page title steps to 18px, stats two across, the w
931
934
 
932
935
  ### Named Rules
933
936
 
934
- **The Panel Scroll Rule.** The page scrolls; a panel does not, except a board column above 900px, a card body inside a dialog, the rail, a window's body (a window is sized by the canvas, not by its content), and a reel, which scrolls sideways, and down where a column is taller than it, its windows sized by its columns. A stage never scrolls. A wide table, a long code line or a run of bar columns scrolls horizontally inside its own panel and never widens the deck.
937
+ **The Panel Scroll Rule.** The page scrolls; a panel does not, except a board column above 900px, a card body inside a dialog, the rail, a window's body (a window is sized by the canvas, not by its content), and a reel, which scrolls sideways, and down where a column is taller than it, its windows sized by its columns. A stage never scrolls, and nor does a bench, whose work scrolls itself. A wide table, a long code line or a run of bar columns scrolls horizontally inside its own panel and never widens the deck.
935
938
 
936
939
  **The 24px Target Rule.** Every target is at least 24px square. A control drawn smaller (a tab, a glyph-switch option, a filter chip, a row toggle, a clickable mark or tag, a text button, a crumb, an error-summary link) keeps its look and takes the pointer over an invisible 24px square centred on it; an icon button never gives up its 24px to a full line (a tag beside it clips first), and links in a list stand 24px apart so no two squares overlap.
937
940
 
@@ -1025,11 +1028,11 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
1025
1028
  - **Places** (a View over `@foldkit/ui`'s Button) → `nav.places[aria-label] > ul > li`, each place (keyed by an `id` that is stable and unique among the places, so two places that share a name stay apart) → `li > a[href]` when the place has an address (an area, a saved view: it opens in a new tab and reads as a link) or `li > button` when choosing it only changes the page, holding `span.place-name` and `span.place-count`, then `span.place-tools`: a column of places to go (areas, collections, saved filters), flush in a rail panel. Each line is at least 34px (`--row`), `8px 12px`, over a 1px rule; the name in the sans, sentence case, wrapping anywhere rather than clipping; the count in the 11px pixel face, muted, at the right; a quiet IconButton after it acts on the place itself (remove a saved filter). Hover tints head. The place you are in carries `aria-current="page"` and its whole `li` prints inverted, the count and a quiet tool in paper at 0.75, never tinted by hover; the ring sits 4px inside the line, in paper on the current one.
1026
1029
  - **PageHead** → `header.page-head > div.page-name > h1.page-title + span.meta`, then `div.page-acts`: the heading of a working screen, one per screen, above its panels (which title in h2). The name in the page-title register with a count or state beside it in the 11px pixel face; the screen's own Buttons at the right, 8px apart, wrapping under the name when the line runs out (`14px × 24px` gaps), aligned to the name's foot. No surface of its own: it stands on the ground. There is no label above the heading; the name says where you are.
1027
1030
  - **TopBar** (a Mount measures it, reporting through `toParentMessage`) → `header.topbar` (brand, nav, `div.topbar-tools`), **Wordmark** → `.wordmark` with `span.cursor`, **LinkState** → `span.link-state[role="status"]`: online prints plain; `.connecting` (the first reach) prints muted with the working cursor; `.reconnecting` (the link dropped and is coming back) fills amber with `on-status` and the cursor; `.offline` (given up) fills red with `on-red`.
1028
- - **StatusBar** (a Mount measures it, reporting through `toParentMessage`) → `footer.statusbar > div.statusbar-state + p.statusbar-keys`: the quiet line along the bottom of an interface, the page's footer (one per page, after the rail and the deck, outside `main`). Fixed to the bottom edge on paper over a 1px rule, in the pixel face at the state size in Muted Ink: the status at the left (a LinkState, a sync word), the keys at the right, each a `kbd.key-hint` in ink (printed as the help prints it, ⌘K or Ctrl+K by platform) and its word, plain text and not pressable. Narrow, the keys wrap under the status; nothing hides. It keeps `--statusbar-h` on the page at its height (25px, one line, until it is measured), like the top bar's `--topbar-h`: the page pads its end by it, a scrolled-to anchor stays clear of it, and the rail, the stage, a Banner and the toast stack all stop above it.
1031
+ - **StatusBar** (a Mount measures it, reporting through `toParentMessage`) → `footer.statusbar > div.statusbar-state + p.statusbar-keys`: the quiet line along the bottom of an interface, the page's footer (one per page, after the rail and the deck, outside `main`). Fixed to the bottom edge on paper over a 1px rule, in the pixel face at the state size in Muted Ink: the status at the left (a LinkState, a sync word), the keys at the right, each a `kbd.key-hint` in ink (printed as the help prints it, ⌘K or Ctrl+K by platform) and its word, plain text and not pressable. Narrow, the keys wrap under the status; nothing hides. It keeps `--statusbar-h` on the page at its height (25px, one line, until it is measured), like the top bar's `--topbar-h`: the page pads its end by it, a scrolled-to anchor stays clear of it, and the rail and its splitter, the stage, a bench, a Banner and the toast stack all stop above it.
1029
1032
 
1030
1033
  ### Chips
1031
1034
 
1032
- - **Chip** → `span.chip` (`.go`, `.warn`, `.blocked`, `.done`; `isBusy` sets `aria-busy="true"` and appends `span.cursor`, the 5×9px working cursor, on a chip that is polling, building or starting): 11px uppercase 0.05em (`--tracking-state`), `padding: 1px 6px`, 2px ink border on paper. `go` fills Active Green and `warn` fills Marker Amber, both with `on-status` text; `blocked` keeps paper with red text and border (also "limit reached"); `done` fills Head Tint with On Head text and a muted border.
1035
+ - **Chip** → `span.chip` (`.go`, `.warn`, `.blocked`, `.done`; `isBusy` sets `aria-busy="true"` and appends `span.cursor`, the 5×9px working cursor, on a chip that is polling, building or starting): 11px uppercase 0.05em (`--tracking-state`), `padding: 1px 6px`, 2px ink border on paper. `go` fills Active Green and `warn` fills Marker Amber, both with `on-status` text; `blocked` keeps paper with red text and border (also "limit reached"); `done` fills Head Tint with On Head text and a muted border. **Chip.dotView** → `span.chip-dot[role="img"][aria-label][data-tip]` (`.go`, `.warn`, `.blocked`, `.done`): the chip with its word taken out, for a line too short to spare a word's width, where the state sits before a title it would otherwise truncate (a task row in a rail). It is a 10px square cell (the system's unit; there is no round dot), 2px bordered: hollow in ink where the chip is plain and filled where it has a tone, so a state not yet started and one under way differ by form, not only hue: green and amber where the chip fills, red for blocked where the chip only outlines, and Muted Ink for done where the chip only tints, since neither an outline nor a tint reads in a 6px cell. Among the filled tones (active, warning, blocked, done) the hue is what tells them apart at a glance; that is the dot's bargain, chosen over a word for the room it gives the title, and why the word is always its name and its tip. In a row it keeps the glyph cell's 18px (4px either side), so titles beside it line up with titles beside a glyph. Neither Mark (one amber meaning) nor LinkState (a connection word, a live status) had a form to borrow, and a dot is a chip's tone without its word, so it is a form of Chip, taking `Chip.Tone`, rather than a component. Its state never rests on its colour: the word is its accessible name and its tip, as a Mark's is. It has no busy form.
1033
1036
  - **FilterChip** → `button.fchip[aria-pressed]` (without `onToggle`, a pinned label, `span.fchip.pinned`; the clear chip is `.reset`): 22px minimum, the name half (`span.fchip-name`) muted; pressed inverts, both halves in paper (a faded name misses AA on plum); hover tints head. `full` writes out a chip whose words alone do not say it ("Reset the filters" for "reset"), as a Tag's does: the button's `aria-label` (on a pinned label, a `span.sr-only` beside the hidden words) and its tip. Chips mapped from a list that changes take a `key`. **QueryChip** → `label.fchip.query` holding a bare sans search input (8–32ch); tinted `.live` while it holds a query. The input lies above the chip's hit square, so a press in it places the caret or selects text; the rest of the chip focuses it. **FilterStrip** → `div.strip`: the paper bar holding them, 3px shadow, `padding: 8px 12px`; a `span.spacer` pushes what follows right.
1034
1037
  - **Tag** → `span.tag` (or `button.tag` with `onClick`): a 10px muted label in a 1px Rule Grey box, `0 5px`, clipped at 18ch; a count, a group, a scope. `full` writes out a tag that is abbreviated or clipped ("3 in Operations" for "3"): it is the tag's name for screen readers (the short text is hidden from them) and its tip. Tags mapped from a list that changes take a `key`.
1035
1038
  - **Registers without a component:** `.label`, `.meta`, `.age` (`.stale` red), `.due` (`.late` red), `.tnum`, `.note-line`, `.done-title`, `.group-name`.
@@ -1052,12 +1055,13 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
1052
1055
 
1053
1056
  - **Panel** → `section.panel[aria-labelledby]` (`[aria-label]` with `name`) `> header.panel-tb > span.panel-lead + h2 + span.meta`, then `div.panel-body` (or the body directly when `isFlush`), then `div.panel-foot`. The section is named by its title, or by `name` where the title repeats among its siblings (panels titled by a kind, one per account), so each landmark keeps a name of its own. It is keyed by its `id`, so a panel among siblings that come and go keeps its own element, and a panel under another id is another element: one that replaces another in the same place is built anew (a live region inside it is inserted, not kept), so keep one panel under one id across states that should share it. Square, 2px ink border, Paper White, panel shadow. The title bar is inverted Departure Mono 13px uppercase 0.08em (`--tracking-title`), `padding: 6px 12px`; its meta is 11px at 75% opacity. A control in a title bar (a panel's, a window's or a card's) rings in paper, 2px inside its own edge, because the ink ring would vanish on the ink bar. The body pads 16px with a 14px grid gap; the foot pads `10px 12px` over a 1px rule, with a `.push` slot that goes right. `actions` puts small Buttons and IconButtons in `span.panel-acts` (6px apart) at the title bar's right end, after the meta; the bar trims to `3px 3px 3px 12px` at 31px or more. `lead` puts a quiet IconButton in `span.panel-lead` before the title (a Sortable's grip), and the bar trims its left to 3px the same way; a quiet icon button there prints in paper, hovered too, and held at 60%. On the ink bar a button is a paper outline (a primary one fills paper), flat, nudged 1px when pressed, its ring 4px inside in its own colour; disabled, it prints at 60%.
1054
1057
  - **Deck** → `main.deck#main`: the column of panels.
1058
+ - **Bench** → `main.bench#main > div.bench-head + div.bench-work`: see Layout. `head` and `body` are both the caller's; beside a Rail it is what `toDeckView` returns, with the open button first in its head.
1055
1059
  - **EmptyState** → `div.empty > h3.empty-title + p`: `headingLevel` is required and picks the title's element, which looks the same at every level. Where the empty state is a page's or a section's main content it is a heading, `3` inside a Panel (whose title is an `h2`), `1` for a gate or a missing page; `"Strong"` prints `strong.empty-title` where it is not (a chart with nothing to chart, a picture of the state). Centred, `26px 16px`, a 12px uppercase 0.08em (`--tracking-title`) headline, a muted 46ch sentence, an optional action 6px below.
1056
1060
  - **Wall** → `main.wall`, `Wall.markView` → `div.wall-mark` (a 30px ink-bordered glyph box beside the 22px wordmark). `Wall.cornerView` → `aside.wall-corner[aria-label]`, rendered just before the Wall: a paper slip in a 2px ink box on the 3px shadow, `6px 8px`, holding what a top bar would (see Layout).
1057
1061
  - **Banner** → `div.banner > span.glyph (.error, .ok, .quiet) + span`: a fixed paper slip on the 3px shadow with a 26px glyph box in the Notice's tones and a sentence that says what happens to the reader's work. It holds a state the whole interface is in, for as long as it lasts, one at a time. It is not a live region (it arrives already holding its sentence, which would go unheard): the application says the state once, when it begins, with the `Announcer.Announce` Command. `"Error"` (the default; red, bang): a state that stops work (offline). `"Warn"` (amber): a state that is passing (reconnecting). `"Ok"` (ink, check): the state just ended (back online), shown briefly and then taken away. `"Quiet"` (head, info): news. With the connection word it is one pair: LinkState `"Reconnecting"` beside a warn Banner, `"Offline"` beside an error Banner with a retry, then `"Online"` beside an ok Banner for a moment; `"Connecting"` takes none.
1058
- - **Rail** (Submodel) → `div.railed > main.deck#main + aside.rail[popover="auto"][aria-label]`: the rail's panels (a tree, places, filters) beside the deck's, after it in the markup so the page's main content and its h1 come first in reading order; the stylesheet fixes the rail at the left under the top bar. One element serves both layouts, so nothing in it renders twice or loses its state: at rest it stands there (its `popover` attribute is inert until opened), and at 900px and under it is hidden and `span.rail-open > button.btn.small[aria-expanded]` at the top of the deck opens it as a drawer: a slice of the dithered ground, `min(320px, 100vw - 40px)` wide from the left edge, closed by a 2px ink rule and the 8px card shadow, entering from the left in three steps over a veil. Its head (`div.rail-head`: the name in the label register and a close IconButton) shows only in the drawer. Opening focuses its first control; Escape, a click on the veil, the close glyph or focus leaving closes it, and all but the last return focus to the button. Widening the window past 900px closes it. The parent reads whether the drawer is open with `Rail.isOpen`, to hold its own page keys while the drawer covers the page. A file tree flush in a rail panel (or any panel) drops its own border.
1062
+ - **Rail** (Submodel) → `div.railed > main.deck#main + aside.rail[popover="auto"][aria-label] + div.rail-resize` (a `main.bench#main` in the deck's place for a page whose work is the window): the rail's panels (a tree, places, filters) beside the deck's, after it in the markup so the page's main content and its h1 come first in reading order; the stylesheet fixes the rail at the left under the top bar. One element serves both layouts, so nothing in it renders twice or loses its state: at rest it stands there (its `popover` attribute is inert until opened), and at 900px and under it is hidden and `span.rail-open > button.btn.small[aria-expanded]` at the top of the deck opens it as a drawer: a slice of the dithered ground, `min(var(--rail-drawer), 100vw - 40px)` (320px) wide from the left edge, whatever width the reader gave the rail, closed by a 2px ink rule and the 8px card shadow, entering from the left in three steps over a veil. Its head (`div.rail-head`: the name in the label register and a close IconButton) shows only in the drawer. Opening focuses its first control; Escape, a click on the veil, the close glyph or focus leaving closes it, and all but the last return focus to the button. Widening the window past 900px closes it. The parent reads whether the drawer is open with `Rail.isOpen`, to hold its own page keys while the drawer covers the page. **Its width is the reader's:** after the aside, `div.rail-resize[role="separator"][aria-orientation="vertical"]` ("Width of <label>", `aria-controls` the rail, its width as `aria-valuenow` between `aria-valuemin` 220 and `aria-valuemax` 480 and as "300 pixels" in `aria-valuetext`) is a 12px strip from the rail's right edge outward, over the page's margin and never over the rail's own scrollbar, fixed beside the rail rather than in it (the rail scrolls, and the drawer's first Tab stop is its first control), a `col-resize` cursor showing a 2px ink line down the edge while hovered or focused, hidden with the rail at 900px and under. A pointer drags it, the rail printing the new width as it moves, and Escape or the browser's cancel puts it back; the side arrows, unmodified, step it 16px, a modified arrow being the application's. Let go where it changes the width, or stepped, it reports `ResizedRail` with the width in whole pixels within the bounds, and the parent keeps it and passes it back as `maybeWidth`: the rail holds no width of its own, as the reel holds no layout. The parent keeps it per client, as it keeps focus and scroll, since a width suits a window, not a task. The splitter's handlers are the reel's (`src/drag`), and the rail's Subscriptions, which follow a dragged splitter and take Escape, are lifted behind a key prefix of the parent's. A file tree flush in a rail panel (or any panel) drops its own border.
1059
1063
  - **Stage** → `main.stage#main > div.stage-work > div.stage-canvas + div.stage-tools + div.stage-bar`: see Layout. `paused` (a Notice saying why: the link dropped, the page is read-only) veils the work when editing stops: `div.stage-paused[role="status"]` lays the dialog veil and its ink grain over everything and centres that Notice on the 3px button shadow, while the work beneath is `inert`. It lifts when `paused` is not given; the connection word says reconnecting meanwhile.
1060
- - **Window** → `section.panel.window (.flush) > header.panel-tb + div.window-body + div.panel-foot`: a Panel for a stage or a reel, as tall and wide as whatever places it (and moved by it, if anything moves it). Its title bar is the panel's, trimmed to hold tools: 30px tall (`3px 3px 3px 10px`, its left trimmed to 3px under a `lead`), centred, with an optional `lead` (`span.panel-lead`, a quiet IconButton: a Reel's grip), an optional kind glyph, the title (clipped with an ellipsis), a status chip (its border turns paper so it reads on ink), tools and a close IconButton, both in the bar's paper. The body scrolls inside (`16px`, a 14px grid); flush, what fills it (a code surface, a file tree, a diff) drops its border and fills it. A foot is a panel foot; a notice there takes the whole line.
1064
+ - **Window** → `section.panel.window (.flush) > header.panel-tb + div.window-body + div.panel-foot`: a Panel for a stage or a reel, as tall and wide as whatever places it (and moved by it, if anything moves it). Its title bar is the panel's, trimmed to hold tools: 30px tall (`3px 3px 3px 10px`, its left trimmed to 3px under a `lead`), centred, with an optional `lead` (`span.panel-lead`, a quiet IconButton: a Reel's grip), an optional kind glyph, the title (clipped with an ellipsis), a status chip (its border turns paper so it reads on ink), tools and a close IconButton, both in the bar's paper. Tools a child Submodel hands back come as one element, `span.window-tools`, which is no box of its own (`display: contents`), so its buttons sit in the bar as the others do. The body scrolls inside (`16px`, a 14px grid); flush, what fills it (a code surface, a file tree, a diff) drops its border and fills it. A foot is a panel foot; a notice there takes the whole line.
1061
1065
  - **Loading** → `div.loading[role="status"]`: where content is on its way, centred like an EmptyState at `26px 16px`, a 12px uppercase 0.08em (`--tracking-title`) muted line ("loading the items") ending in the 6×11px working cursor. Its status element stays in the page and only its words change: with `isLoading` false it is `div.loading-idle[role="status"]`, empty and out of the flow (`position: absolute`, never `display: none`, which would drop the live region), so it takes no room and no gap at the top of a panel's or a card's body, and a screen reader hears the line when it arrives. The content or an EmptyState takes its place on screen; the line never prints beside either. It is not the page's live region (the Announcer's).
1062
1066
  - **Remote** → spread where content goes: data that loads, in each of Foldkit's `AsyncData` states, with the retry built in, so a failure is never shown without its way back. A Loading line (`loading <label>`) leads in every state, in place, so its status is heard, and holds its words only while the first load runs: a refresh over data already shown is quiet, so a page that polls is not heard each time. Retry takes its button away with the notice, so the update returns focus to the line (its `id`) with the retry, where a first load is heard. A failure with nothing to show is an error Notice titled "<Label> not loaded", the reason (`toErrorText`) under it and a small "retry" button (`onRetry`); a refresh that failed over data already shown (stale) is a warn Notice titled "<Label> not refreshed" with the same reason and retry, above the last data; loaded and refreshing data is the caller's `toDataView` alone, an EmptyState for none included.
1063
1067
  - **Disclosure** (a View over `@foldkit/ui`'s Disclosure; the caller keeps whether it is open) → `div.disclosure > button.disclosure-summary[aria-expanded] + div.disclosure-body`. The summary is a control-register line (`3px 0`) of a 12px `caret-right` glyph, `span.disclosure-label` (a dotted underline on hover) and an optional `span.meta` pushed right; open, the caret turns a quarter in one step (a right angle keeps the grid). The body hangs from the caret on a 1px Rule Grey guide, its content level with the summary's words (`margin-left: 5px`, `padding-left: 14px`), in a 14px grid. For what a reader opens on purpose (grants to edit, details, a danger zone), never to hide the one thing the screen is for.
@@ -1086,7 +1090,7 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
1086
1090
 
1087
1091
  ### Tips and toolbars
1088
1092
 
1089
- **Tips** (Submodel; `Tips.subscriptions(keyPrefix)` follow the pointer, focus and keys across the page, keyed behind the parent's prefix: `Tips.subscriptions("tip")` is keyed `tipPointerAndFocus` and `tipKeys`) → one `div.tip[popover="manual"][aria-hidden="true"]` for the page, rendered once beside the Announcer, which shows the words of any element carrying `data-tip`: a label, not a surface, so flat Printer Ink with Paper White text and no print shadow, ringed by a 2px paper outline so it parts from an ink bar under it; the 11px pixel face, uppercase, 0.05em (`--tracking-state`), `3px 7px`, at most 36ch. It is placed by anchor positioning 6px above its control (the control is the tip's source, so its implicit anchor), hangs from the control's side near a window edge and flips below near the top; inside a vertical strip it opens to the right. It opens on a resting pointer after 400ms (at once while another tip has just closed, so sweeping a toolbar reads each name), never on touch, and on keyboard focus (`:focus-visible`) a beat later, only if the control is still on the page and still the keyboard's, so focus a closing dialog or menu hands back does not open a tip mid top-layer change; it can be hovered, it closes on leave and blur (which also cancels a tip still waiting), and using the control (a press, any key) closes it, so a control that renames itself is read afresh next time. Escape closes a tip that focus opened, and nothing else, never the menu or card around it. **The Tip Rule:** a tip shows its element's accessible name and nothing else, which is why it stays out of the accessibility tree; a control that needs a description says it in words beside it. IconButton, the glyph switch, Mark, the board grip, a menu's glyph toggle and an abbreviated Tag carry one. A pressed toggle keeps its name and lets `aria-pressed` carry the change. The system uses no `title` attribute tooltips.
1093
+ **Tips** (Submodel; `Tips.subscriptions(keyPrefix)` follow the pointer, focus and keys across the page, keyed behind the parent's prefix: `Tips.subscriptions("tip")` is keyed `tipPointerAndFocus` and `tipKeys`) → one `div.tip[popover="manual"][aria-hidden="true"]` for the page, rendered once beside the Announcer, which shows the words of any element carrying `data-tip`: a label, not a surface, so flat Printer Ink with Paper White text and no print shadow, ringed by a 2px paper outline so it parts from an ink bar under it; the 11px pixel face, uppercase, 0.05em (`--tracking-state`), `3px 7px`, at most 36ch. It is placed by anchor positioning 6px above its control (the control is the tip's source, so its implicit anchor), hangs from the control's side near a window edge and flips below near the top; inside a vertical strip it opens to the right. It opens on a resting pointer after 400ms (at once while another tip has just closed, so sweeping a toolbar reads each name), never on touch, and on keyboard focus (`:focus-visible`) a beat later, only if the control is still on the page and still the keyboard's, so focus a closing dialog or menu hands back does not open a tip mid top-layer change; it can be hovered, it closes on leave and blur (which also cancels a tip still waiting), and using the control (a press, any key) closes it, so a control that renames itself is read afresh next time. Escape closes a tip that focus opened, and nothing else, never the menu or card around it. **The Tip Rule:** a tip shows its element's accessible name and nothing else, which is why it stays out of the accessibility tree; a control that needs a description says it in words beside it. IconButton, the glyph switch, Mark, a chip's dot, the board grip, a menu's glyph toggle and an abbreviated Tag carry one. A pressed toggle keeps its name and lets `aria-pressed` carry the change. The system uses no `title` attribute tooltips.
1090
1094
 
1091
1095
  **Toolbar** (a Mount moves focus, reporting through `toParentMessage`) → `div.toolbar[role="toolbar"][aria-orientation]` (`.floating`): one strip of tools that is one stop for Tab: its own buttons and links, not the items of a menu one of them opens. Arrows on either axis move between tools and stop at the ends, as a cursor's, the palette's and a dialog's action bar's do (a menu and tabs wrap, as their platform patterns do; a vertical strip lies flat along the bottom of a stage on a phone), Home and End go to the ends, and the tool last used is where Tab returns (a roving `tabindex`). The strip is a 2px ink box whose ink shows through a 2px gap between tools, so the rules between them never change with a tool's state. Tools are 28px tall, glyph-only IconButtons 30px wide, labelled Buttons `0 10px` in the 11px pixel face, all paper, head on hover, inverted when `aria-pressed` (the tool in force), muted when disabled, nudged 1px while pressed. A tool's focus ring sits 4px inside it in the tool's own text colour, so it reads on a pressed tool and never lands on the strip's ink. Floating (over a canvas) it takes the 3px button shadow; vertical, its tips open to the right.
1092
1096
 
@@ -1098,7 +1102,7 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
1098
1102
 
1099
1103
  ### Rows and the outline
1100
1104
 
1101
- **Rows** → `ul.rows` (`.tree` for an outline); each row, `Rows.rowView` → `li > div.row` (`.context`) with an optional nested `ul.rows`. A context row (outside a filter, printed only to keep a match on its chain) is muted and carries no chips, and since muting is colour alone it ends its text with `span.sr-only` " (context)", which a selectable row's box takes into its name. A 34px flex row (`5px 12px`, 8px gap): an 18px glyph cell (`Rows.glyphView` → `span.glyph`, or `Rows.toggleView` → `button.glyph[aria-expanded]` with the ring glyph open or closed, named "Rows under" its label whether open or not), `div.grow` holding the text (a `label.grow` when it is the label of the row's box; `Rows.titleView` → `button.title`, underlined on hover, `.done-title` when done), `span.slot` for chips, marks and due days, the row's actions, and `span.tools` (`tools`, icon buttons) resting at 55% opacity until hover or focus-within (hidden on phones). Hover and focus-within tint the row head with `background-color` only, so the indent guides survive; the cursor row takes neither. A keyboard-driven list is a treegrid: `ul.rows[role="treegrid"]` named by its label, each `li` and nested `ul` stepping aside (`role="none"`), each `div.row[role="row"]` carrying `aria-level`, `aria-posinset`, `aria-setsize` and, on a row that folds, `aria-expanded`, and each of its parts a `role="gridcell"` (the box's `span.row-check`, the text, `span.slot`, `span.tools`, and `span.cell` around the glyph and around the actions, laid out as the row lays them out). It puts its cursor on `div.row` (`aria-current="true"`, a roving `tabindex`), which the stylesheet outlines (see "The cursor" below), through `Rows.rowView`'s `attributes`, which go on `div.row` rather than its `li` and also carry the row's name and its key handlers (never a class). A list no keyboard drives stays `ul > li`; a row there named for a screen reader takes `role="group"` with its name. Nested lists indent 26px on a 1px rule guide. **InlineInput** (Submodel; its `init` focuses and selects the field, so fold it in with `Update.foldChildInit`) → `input.inline-input` swaps in for text at exactly `1lh + 1px` so the row never moves. Enter or leaving the field reports new, non-empty text, trimmed, as `Committed`; an untouched field reports nothing, so a remote edit is never written back. The field then holds `readonly` and `aria-busy` (muted, still focused) until the parent answers with `InlineInput.succeeded` or `InlineInput.failed`: a failure keeps the field and its text for another try and, when it is a refusal with a reason, says why through `InlineInput.errorView`, which the field points at (`aria-invalid`, a red rule, and `aria-describedby`); the parent reads the held reason with `InlineInput.maybeError`. The parent's own `error` (the item's parent was deleted while editing) marks the field the same way, given to the view and to `errorView` alike, and shows before a refused save's reason. `Ended` says the edit is over (`"Enter"`, `"Blur"` or `"Escape"`, and `"Blur"` when focus left while the save was out) with the id the edit's last save returned (`maybeLastSavedId`, the item a repeat added last); the parent moves focus on `"Enter"` and `"Escape"` only, so a clicked destination keeps it. `isRepeating` (adding several) empties the field after each saved Enter instead of ending; Enter on an empty one ends. The text lives in the Submodel's Model, so it outlives a render. Escape is stopped from closing a dialog around it; an IME's Enter and Escape stay with the IME; removing the field writes nothing. **EditableTitle** (Submodel; it reports `Committed`, which the parent answers with `EditableTitle.succeeded` or `failed`) → `button.title-display` ⇄ `input.inline-input.title-edit` at 16px/600; Enter and Escape put focus back on the title.
1105
+ **Rows** → `ul.rows` (`.tree` for an outline); each row, `Rows.rowView` → `li > div.row` (`.context`) with an optional nested `ul.rows`. A context row (outside a filter, printed only to keep a match on its chain) is muted and carries no chips, and since muting is colour alone it ends its text with `span.sr-only` " (context)", which a selectable row's box takes into its name. A 34px flex row (`5px 12px`, 8px gap): an 18px glyph cell (`Rows.glyphView` → `span.glyph`, or `Rows.toggleView` → `button.glyph[aria-expanded]` with the ring glyph open or closed, named "Rows under" its label whether open or not), `div.grow` holding the text (a `label.grow` when it is the label of the row's box; `Rows.titleView` → `button.title`, underlined on hover, `.done-title` when done), `span.slot` for chips, marks and due days (it keeps 58px, so a status that must not cost the title its room goes before it instead, as a `Chip.dotView` in the glyph cell), the row's actions, and `span.tools` (`tools`, icon buttons) resting at 55% opacity until hover or focus-within (hidden on phones). Hover and focus-within tint the row head with `background-color` only, so the indent guides survive; the cursor row takes neither. A keyboard-driven list is a treegrid: `ul.rows[role="treegrid"]` named by its label, each `li` and nested `ul` stepping aside (`role="none"`), each `div.row[role="row"]` carrying `aria-level`, `aria-posinset`, `aria-setsize` and, on a row that folds, `aria-expanded`, and each of its parts a `role="gridcell"` (the box's `span.row-check`, the text, `span.slot`, `span.tools`, and `span.cell` around the glyph and around the actions, laid out as the row lays them out). It puts its cursor on `div.row` (`aria-current="true"`, a roving `tabindex`), which the stylesheet outlines (see "The cursor" below), through `Rows.rowView`'s `attributes`, which go on `div.row` rather than its `li` and also carry the row's name and its key handlers (never a class). A list no keyboard drives stays `ul > li`; a row there named for a screen reader takes `role="group"` with its name. **A row that is its link** (`isLink` → `div.row.linked`): where a row's text is one link (a task in a sidebar, a page), the whole row is that link's target, as a selectable row's text is its box's label, so the reader presses the row, not the words. The link's `::after` covers the row (the row places it; the text's clip, having no position, does not cut it), and only the row's controls (the box, a fold toggle, buttons and fields in the actions and tools) and whatever carries a tip (a dot) sit above it, so a menu stays its own target and a dot's tip still shows, while a chip or a due day in the slot is the link's like the rest of the row. The row takes no Tab stop of its own: the link is its text's one stop, and its controls keep theirs. The link is plain ink with no underline at rest and underlined while the row is hovered; its ring is drawn round the row, 2px ink 4px inside it, not round the words. A link that is `aria-current` prints its row inverted, as a place you are in and a board's cursor card do: a dot's border takes the paper, what prints muted on paper (a glyph, a due day, an age, a tag, a quiet tool) prints in paper at 0.75, lateness takes a red slip, hover neither tints nor underlines it, and a ring inside prints in paper. It is for a list no keyboard drives: a treegrid's row is already its own stop, so `isLink` with `treegrid` is ignored. A folding row's glyph cell holds its toggle, so its dot goes first in its text, before the link, where its tip still lifts it above the link. Nested lists indent 26px on a 1px rule guide. **InlineInput** (Submodel; its `init` focuses and selects the field, so fold it in with `Update.foldChildInit`) → `input.inline-input` swaps in for text at exactly `1lh + 1px` so the row never moves. Enter or leaving the field reports new, non-empty text, trimmed, as `Committed`; an untouched field reports nothing, so a remote edit is never written back. The field then holds `readonly` and `aria-busy` (muted, still focused) until the parent answers with `InlineInput.succeeded` or `InlineInput.failed`: a failure keeps the field and its text for another try and, when it is a refusal with a reason, says why through `InlineInput.errorView`, which the field points at (`aria-invalid`, a red rule, and `aria-describedby`); the parent reads the held reason with `InlineInput.maybeError`. The parent's own `error` (the item's parent was deleted while editing) marks the field the same way, given to the view and to `errorView` alike, and shows before a refused save's reason. `Ended` says the edit is over (`"Enter"`, `"Blur"` or `"Escape"`, and `"Blur"` when focus left while the save was out) with the id the edit's last save returned (`maybeLastSavedId`, the item a repeat added last); the parent moves focus on `"Enter"` and `"Escape"` only, so a clicked destination keeps it. `isRepeating` (adding several) empties the field after each saved Enter instead of ending; Enter on an empty one ends. The text lives in the Submodel's Model, so it outlives a render. Escape is stopped from closing a dialog around it; an IME's Enter and Escape stay with the IME; removing the field writes nothing. **EditableTitle** (Submodel; it reports `Committed`, which the parent answers with `EditableTitle.succeeded` or `failed`) → `button.title-display` ⇄ `input.inline-input.title-edit` at 16px/600; Enter and Escape put focus back on the title.
1102
1106
 
1103
1107
  **The cursor:** `div.row[aria-current="true"]`, set on the row by the application, the one row the keyboard's commands act on. It is drawn, not filled: a 1px Muted Ink outline 4px inside the row (`outline-offset: -4px`), so a nested row's follows its indent and none leaves the panel. It takes no fill at all, not even the hover or focus tint other rows take, and its text, chips and tools keep their own colours (the tools at full opacity). Focus on the row itself thickens the same line to 2px (`--line`): one line inside the row, never a second ring outside it, for any focused row. It is neither focus nor selection (the check cell), it stays put when focus leaves, and moving it is the application's (see Keys). Controls in it keep their own ring: the text cell (`.grow`) clips, but with 4px of room inside the clip, so a title's ring prints whole; an InlineInput in a row shows its focus as its caret and its rule doubled to 2px (drawn inside its box, so the row keeps its height; red while refused), not a ring. A board's cursor card is inverted instead (see Board).
1104
1108
 
@@ -1114,13 +1118,15 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
1114
1118
 
1115
1119
  ### Reel
1116
1120
 
1117
- **Reel** (Submodel, holding its own drag state rather than `@foldkit/ui`'s DragAndDrop, whose targets are positions in a list and whose pointer reads the items under it; for a workspace of windows side by side: terminals, an editor, a browser, a canvas, in columns the reader arranges) → `div.reel[role="group"][aria-label][data-reel]` holding the scroller `div.reel-view`, whose `div.reel-track` holds, in order, `div.reel-gap` (the gap before the first column), then per column `div.reel-column[data-reel-column]` (the column's id; its share in `--reel-width`) with `div.reel-column-head` (the column's grip and `span.reel-width`, its share in the state register: `1/2`, `full`), a `div.reel-slot` per pane (`--reel-weight`, `--reel-anchor`) and between two slots `div.reel-split[role="separator"][aria-orientation="horizontal"]` ("Height of <pane>", `data-reel-split` the id of the pane above it), and after each column `div.reel-gap.reel-resize[role="separator"][aria-orientation="vertical"]` ("Width of column 2", its share as `aria-valuenow` and in words as `aria-valuetext`, `data-reel-resize` its column's id), then `div.reel-panes` with a `div.reel-pane[data-reel-pane][tabindex="-1"]` per pane, each the parent's Window with the pane's grip as its `lead`; after the scroller, the grips' keyboard help in a `p.sr-only` and, while a pointer carries a pane or a column, `div.reel-lift` with its copy. The reel takes its height from whatever holds it. The layout is the parent's (`Reel.Layout`: columns, each an id, a width and its panes, each an id, a label and a weight), passed to the view and, as `{ layout }`, to `update`, so the reel never holds a copy that could go stale. Whenever the parent changes the layout (a request it applied, another client's, a pane it closed), it calls `Reel.reconcile` with the layout as it is now.
1121
+ **Reel** (Submodel, holding its own drag state rather than `@foldkit/ui`'s DragAndDrop, whose targets are positions in a list and whose pointer reads the items under it; for a workspace of windows side by side: terminals, an editor, a browser, a canvas, in columns the reader arranges) → `div.reel[role="group"][aria-label][data-reel]` holding the scroller `div.reel-view`, whose `div.reel-track` holds, in order, `div.reel-gap` (the gap before the first column), then per column `div.reel-column[data-reel-column]` (the column's id; its share in `--reel-width`) with a `div.reel-slot` per pane (`--reel-weight`, `--reel-anchor`) and between two slots `div.reel-split[role="separator"][aria-orientation="horizontal"]` ("Height of <pane>", `data-reel-split` the id of the pane above it), and after each column `div.reel-gap.reel-resize[role="separator"][aria-orientation="vertical"]` ("Width of column 2", its share as `aria-valuenow` and in words as `aria-valuetext`, `data-reel-resize` its column's id), then `div.reel-panes` with a `div.reel-pane[data-reel-pane][tabindex="-1"]` per pane, each the parent's Window with the pane's grip as its `lead`; after the scroller, the grips' keyboard help in a `p.sr-only` and, while a pointer carries a pane, `div.reel-lift` with its copy. The reel takes its height from whatever holds it. The layout is the parent's (`Reel.Layout`: columns, each an id, a width and its panes, each an id, a label and a weight), passed to the view and, as `{ layout }`, to `update`, so the reel never holds a copy that could go stale. Whenever the parent changes the layout (a request it applied, another client's, a pane it closed), it calls `Reel.reconcile` with the layout as it is now.
1122
+
1123
+ **A column has no head.** It is its panes and nothing else: no grip, no width mark, no bar above them, so the only title bars in a reel are its windows'. A head read as a second header over each pane, and its mark (`1/2`) said what the width splitter already says (`aria-valuetext`) and `CycleWidth` says aloud. This holds for every reel, not as an option: the adopters it serves both want the columns bare, and an input for a head nobody asks for would be API for a hypothetical adopter. What the head did is done elsewhere: a pane's grip drags it into another column, between two of its panes or into a gap as a column of its own; a column is sized by the splitter after it and by `CycleWidth`; and a column moves by the application's keys (`MoveColumnLeft`, `MoveColumnRight`), not by a pointer. The live region's words still count columns ("Notes, 2 of 2 in column 2 of 3.", "Width of column 2"), which a reader counts from the left without a head to read the number from.
1118
1124
 
1119
1125
  **The Pane Order Rule.** The panes are in the page in one order, by id, whatever their layout, and each is drawn over its slot by anchor positioning (`anchor-name` on the slot, `position-anchor` and `anchor()` insets on the pane; the track is the containing block and the pane layer is `display: contents`). A move changes where a pane is drawn and never its place in the page, so a terminal, a video or an embedded browser keeps its focus, its scroll and its state through any move, and a new pane is inserted among the others without moving them. So Tab follows ids, not columns: give panes ids that sort by when they were made (ULID, UUIDv7) and the reading order is the order they were opened; the keys below move by place.
1120
1126
 
1121
1127
  **The Id Rule.** Everything the reel asks of the layout names panes and columns by id, never by position: a pane into a column at a place among its panes, a pane into a column of its own before a column (or after the last), a column before another, a column's width, its panes' weights by pane id. So a request means what it meant after the layout changed under it, applied at once, later, on a server or after another client's move, and what is held keeps its target the same way. A request naming a pane or column the layout no longer has changes nothing. A column `Reel.apply` makes for a pane pulled out takes the pane's id (`notes`, or `notes/2` where a column has it), so pane and column ids share one namespace.
1122
1128
 
1123
- Each pane's grip, and each column's, is a quiet `button.iconbtn[data-reel-grip]` (the grip glyph, "Move <pane>" or "Move column 2", `aria-roledescription="draggable"`, `aria-pressed`, described by the help). Only a grip lifts, under a mouse, a finger or a pen (`cursor: grab`, `touch-action: none`), so a pane's text selects and a terminal takes the pointer; it carries once the pointer has moved 5px. A held pane dims to 40% in place (`.drag-dim`); its copy, drawn by the reel from the labels (the title bars alone, tinted head on the 8px card shadow, `aria-hidden`, `inert`, no ids), follows the pointer, the panes of a held column stacked in it, so whatever a pane holds is never built twice. Where it would drop, a 2px dashed ink line prints down the gap it would open (`.drop-here`, a new column there for a pane, the column's new place for a column) or across the gutter between the panes it would join (`.drop-before`, `.drop-after`), over the layout, so nothing moves under the pointer; near either edge of the reel the reel scrolls under a carried pane, by time, not frames. On the keyboard, Space or Enter on a grip picks it up and the grip takes the keys while it holds it: ← and → step from a column to the gap beside it and on to the next, ↑ and ↓ move among a column's panes, and Space or Enter drops it. Escape puts it back, taken before a dialog sees it, and so does focus leaving the grip (Tab, a click elsewhere), so a field reached by Tab types as it should. A stop where the drop would change nothing is passed over, but for where it started. A column dropped by the keyboard gets its grip's focus back, since a column's element moves with it. Each step is said through the page's live region ("Notes, 2 of 2 in column 2 of 3.", and at a gap "Notes, column 4 of 4.", the new column it would make). A held pane or column that leaves the layout cancels the move ("Move cancelled."), and one held over a column that leaves aims back where it started, so the arrows go on from there.
1129
+ Each pane's grip is a quiet `button.iconbtn[data-reel-grip]` (the grip glyph, "Move <pane>", `aria-roledescription="draggable"`, `aria-pressed`, described by the help). Only a grip lifts, under a mouse, a finger or a pen (`cursor: grab`, `touch-action: none`), so a pane's text selects and a terminal takes the pointer; it carries once the pointer has moved 5px. A held pane dims to 40% in place (`.drag-dim`); its copy, drawn by the reel from its label (the title bar alone, tinted head on the 8px card shadow, `aria-hidden`, `inert`, no ids), follows the pointer, so whatever a pane holds is never built twice. Where it would drop, a 2px dashed ink line prints down the gap it would open (`.drop-here`, a new column there) or across the gutter between the panes it would join (`.drop-before`, `.drop-after`), over the layout, so nothing moves under the pointer; near either edge of the reel the reel scrolls under a carried pane, by time, not frames. On the keyboard, Space or Enter on a grip picks it up and the grip takes the keys while it holds it: ← and → step from a column to the gap beside it and on to the next, ↑ and ↓ move among a column's panes, and Space or Enter drops it. Escape puts it back, taken before a dialog sees it, and so does focus leaving the grip (Tab, a click elsewhere), so a field reached by Tab types as it should. A stop where the drop would change nothing is passed over, but for where it started. Each step is said through the page's live region ("Notes, 2 of 2 in column 2 of 3.", and at a gap "Notes, column 4 of 4.", the new column it would make). A held pane that leaves the layout cancels the move ("Move cancelled."), and one held over a column that leaves aims back where it started, so the arrows go on from there.
1124
1130
 
1125
1131
  The splitters size by pointer or by the side arrows (a width) and up and down (a height), 5% of the reel or the column a press, unmodified only: a modified arrow is the application's. A splitter moves a share of the reel's width, or of its column's height, from the sizes the layout holds, never the sizes printed, which the stylesheet may hold at their least: a width goes no narrower than a tenth, and a pane keeps a twentieth of its column, and the stylesheet keeps both at their least on screen. A height splitter sizes the pane above it, named by id, against whichever pane is below it in the layout when the drag moves, so a reorder while it is dragged never resizes a pane in its place; it lets go when the pane above it has no pane below. While a splitter is dragged the reel prints the new sizes as a preview and asks for the size only when let go; the browser cancelling the pointer, or Escape, puts it back.
1126
1132
 
@@ -1130,7 +1136,7 @@ Focus is the reel's: one pane is focused (`.focused`, its window on the 8px card
1130
1136
 
1131
1137
  ### Inputs / Fields
1132
1138
 
1133
- - **Field** (a View over `@foldkit/ui`'s Input) → `div.field (.boxed, .mono) > label + input + p.field-error + p.field-help`, the label naming the input and the help describing it; a failed field prints why in `p.field-error` (sans, Urgent Red) under the control, which is `aria-invalid` and described by it before its help, and the ErrorSummary above the fields says it again; a required field's control is `aria-required`; the input's `type` the platform's (`"date"` for the browser's date control) when given. **SelectField** (over `@foldkit/ui`'s Select) → `div.field > label + select + p.field-error + p.field-help`, its options in order and a group's under an `optgroup` heading, an option that is listed but not choosable `disabled`; **TextareaField** (over `@foldkit/ui`'s Textarea) → `div.field (.mono) > label + textarea + p.field-error + p.field-help`, opening at `rows` lines. Both name, describe and mark their control as a Field does. **Fieldset** → `fieldset.field > legend.field-label`, then the controls, `p.field-error` and `p.field-help`, its error and its help linked with `aria-describedby`; **FieldGrid** → `div.field-grid` (auto-fit, 160px minimum); **Fields** → `fieldset.stack.gap-m`: a form's fields as one column `--space-m` (14px) apart, a body's gap, on the layout primitives with no rule of its own. `isBusy` (a save in flight) sets `disabled` and `aria-busy` on it, so every control holds at once and nothing changes under the save; the submit Button stays outside it, `isBusy`, so it keeps focus. A disabled control prints its value in Muted Ink; a boxless one also drops its dotted baseline (the mark of a place to type), a boxed one or a select mutes its box and caret.
1139
+ - **Field** (a View over `@foldkit/ui`'s Input) → `div.field (.boxed, .mono) > label + input + p.field-error + p.field-help`, the label naming the input and the help describing it; a failed field prints why in `p.field-error` (sans, Urgent Red) under the control, which is `aria-invalid` and described by it before its help, and the ErrorSummary above the fields says it again; a required field's control is `aria-required`; the input's `type` the platform's (`"date"` for the browser's date control) when given. A field whose place says what it is (a browser's address bar) hides its label (`label.sr-only`), which still names the input. **SelectField** (over `@foldkit/ui`'s Select) → `div.field > label + select + p.field-error + p.field-help`, its options in order and a group's under an `optgroup` heading, an option that is listed but not choosable `disabled`; **TextareaField** (over `@foldkit/ui`'s Textarea) → `div.field (.mono) > label + textarea + p.field-error + p.field-help`, opening at `rows` lines. Both name, describe and mark their control as a Field does. **Fieldset** → `fieldset.field > legend.field-label`, then the controls, `p.field-error` and `p.field-help`, its error and its help linked with `aria-describedby`; **FieldGrid** → `div.field-grid` (auto-fit, 160px minimum); **Fields** → `fieldset.stack.gap-m`: a form's fields as one column `--space-m` (14px) apart, a body's gap, on the layout primitives with no rule of its own. `isBusy` (a save in flight) sets `disabled` and `aria-busy` on it, so every control holds at once and nothing changes under the save; the submit Button stays outside it, `isBusy`, so it keeps focus. A disabled control prints its value in Muted Ink; a boxless one also drops its dotted baseline (the mark of a place to type), a boxed one or a select mutes its box and caret.
1134
1140
  - **Label:** 10px uppercase 0.1em Muted Ink, 4px above; turns red when the field is invalid, as a fieldset's legend does when a control in it is.
1135
1141
  - **Boxless (default):** full width, sans body, no box, a 1px dotted Muted Ink baseline, `padding: 2px 0`. On keyboard the baseline becomes 2px solid ink; invalid, 2px solid red.
1136
1142
  - **Boxed:** the field that is the whole job of its form (sign-in, a quick-add field): 2px ink border, `padding: 5px 8px`; keyboard ring 2px outside; invalid turns the border red. A date is always boxed.
@@ -1164,7 +1170,7 @@ Focus is the reel's: one pane is focused (`.focused`, its window on the 8px card
1164
1170
  - **CodeSurface** → `pre.code-surface[tabindex="0"][role="group"]`, named by its label: a 2px ink-bordered head-tinted block in 12px/1.6 system mono, `10px 12px`, `tab-size: 2`, wrapping. It takes a string or a highlighter's spans: `tok-keyword`; `tok-string`, `tok-string2`, `tok-url`; `tok-number`, `tok-bool`, `tok-atom`, `tok-literal`; `tok-definition`, `tok-variableName2`, `tok-macroName`, `tok-link` (underlined); `tok-typeName`, `tok-className`, `tok-namespace`; `tok-comment`, `tok-meta`, `tok-punctuation` take their syntax roles; `tok-heading` and `tok-strong` are bold, `tok-emphasis` italic, `tok-inserted` green ink, `tok-deleted` and `tok-invalid` red ink (invalid on a dotted underline). Anything unnamed stays ink. **CodeEditor** (a View over `@foldkit/ui`'s Textarea) → `textarea.code-surface`, spelling, capitals and autocomplete off: 220px minimum, grows to 60vh, vertically resizable, scrolls sideways (wraps on phones).
1165
1171
  - **Secret** → `div.secret > code[role="group"] + button.btn.small`, the code named by its label: the same box at `6px 6px 6px 12px` with a scrolling nowrap code at 0.02em (wrapping anywhere on a phone, so the end is never hidden) and, in its `actions`, a copy button whose word turns "copied". **CopyButton** (Submodel; `CopyButton.isCopied` is for a parent that follows the copy) → the quiet copy glyph (a check once copied) or, as `"Button"`, a small button whose word turns "copied", for a beat; either says it to screen readers, and a copy that fails says so. It writes through Effect's `Clipboard` service: the application provides `Clipboard.layer` from `@effect/platform-browser` in its `resources`. A `div.save-row` with a `span.push` holds what follows it (the "I've stored it" commit).
1166
1172
  - **History** → `ol.history-list (.checks) > li (.current, [aria-current="true"]) > span.v + span.who + span.what (.failed)`: ruled rows, the version in 11px pixel face in a fixed `4ch + 8px` box (inverted when current, so nothing moves, a Time in it taking the paper too), who in 10px muted at 10ch (20ch for checks), what in sans, a failed line red with "(failed)" after it for screen readers (`span.sr-only`), so the state never rests on colour. `isWrapped` → `ol.history-list.wrapped` (never `.wrap`, which is Wrap's row), for an activity log whose words matter more than a tidy column, compact enough for a rail's panel: when (a whole timestamp, its box as wide as it, muted and flush with the what under it, padded `0 4px` and inverted only when current) and who share the first line with the action pushed to its end, and what runs on its own line below in the note size (12px/1.45 sans, in ink), wrapping anywhere. Entries are `6px 0` apart over 1px rules with a 2px gap between their lines; there is no rule above the first and no padding before the first or after the last, so the list starts where a body's content starts.
1167
- - **LineDiff** → `div.diff-wrap > table.diff > tr.add | tr.del | tr.same | tr.fold`: a Myers line diff (jsdiff's `diff`, a runtime dependency) in a 2px ink box on paper that scrolls sideways; 12px/1.6 system-mono lines, each with two 10px pixel-face line numbers behind a 1px rule, a sign column, and the line. Added lines are Head Tint behind an ink `+`; deleted lines muted behind a red `−`; unchanged runs beyond two lines of context fold into a head-tinted row labelled in 10px uppercase On Head. On phones lines wrap. `LineDiff.patchView` draws the same table from one file's unified diff, the lines between its hunks folded into the counted row; anything else prints a note line.
1173
+ - **LineDiff** → `div.diff-wrap > table.diff > tr.add | tr.del | tr.same | tr.fold`: a Myers line diff (jsdiff's `diff`, a runtime dependency) in a 2px ink box on paper that scrolls sideways; 12px/1.6 system-mono lines, each with two 10px pixel-face line numbers behind a 1px rule, a sign column, and the line. Added lines are Head Tint behind an ink `+`; deleted lines muted behind a red `−`; unchanged runs beyond two lines of context fold into a head-tinted row labelled in 10px uppercase On Head. On phones lines wrap. `LineDiff.patchView` draws the same table from one file's unified diff, the lines between its hunks folded into the counted row. What git's header says prints as one note line, above the table or in its place: a rename (its path unquoted), a mode change, a new or deleted empty file, a binary change; a file whose type changed (a file that became a symlink) is one note over a table for each side, named for its side, the note and tables in a `div.stack.gap-s`. A lone table or note is drawn bare, so a flush window's body still meets it. Text that is not a diff, or holds several files, prints a note line that says it cannot be shown as lines. `LineDiff.describePatch` gives the notes, for a caller that prints them itself.
1168
1174
 
1169
1175
  ### Meters and cells
1170
1176
 
@@ -1221,7 +1227,7 @@ Installing: see README.md beside this file. The package is a Foldkit library. It
1221
1227
 
1222
1228
  The peers in `package.json` are pinned to the Foldkit line pixel-paper is built and tested against. Foldkit is pre-1.0, so a caret range admits patches only; a new Foldkit minor is a new pixel-paper release.
1223
1229
 
1224
- Without Foldkit: link `pixel-paper.css` and write the markup named in each component above. The stylesheet relies on these attributes: `aria-current` (`="page"` on a nav segment, a crumb, a menu link and a place's button; `="true"` on `.row` and `.board-card` for the cursor, given `tabindex="0"` while the others take `-1`, on the open file and on a history's current entry), `aria-selected`, `aria-pressed`, `aria-checked`, `aria-expanded`, `aria-disabled` (on buttons, on a board card's `.title`, on menu items, on a select-all box), `aria-invalid`, `disabled` and `aria-busy` on the Fields `fieldset` while a save is in flight, `aria-hidden="true"` on a button's or menu item's `kbd.key-hint`, `data-label` on stacked-table cells, `data-drag-over` on a drop zone while a file hovers, `data-pixel-paper-dragging` on the root while a sortable's item or a reel's pane is pressed or carried, `--reel-width`, `--reel-weight` and `--reel-anchor` on a reel's columns, slots and panes, `data-reel-focus` on the control in a pane focus lands on, `data-tip` on glyph-only controls (the words the page's one tip shows), native `<dialog>` for overlays and native `[popover]` for the rail's drawer and tips. Behaviour the components own (board moves, a reel's moves, sizes and focus, a menu's placement and arrow keys, a context menu's placement and focus, tips, a toolbar's roving focus, the palette cursor, tab arrows, focus into dialogs, an error summary's focus, the flash, the remembered palette, the toast stack's hold and focus, a player's controls, the key help) is the adopter's to write.
1230
+ Without Foldkit: link `pixel-paper.css` and write the markup named in each component above. The stylesheet relies on these attributes: `aria-current` (`="page"` on a nav segment, a crumb, a menu link, a place's button and a linked row's link; `="true"` on `.row` and `.board-card` for the cursor, given `tabindex="0"` while the others take `-1`, on the open file and on a history's current entry), `aria-selected`, `aria-pressed`, `aria-checked`, `aria-expanded`, `aria-disabled` (on buttons, on a board card's `.title`, on menu items, on a select-all box), `aria-invalid`, `disabled` and `aria-busy` on the Fields `fieldset` while a save is in flight, `aria-hidden="true"` on a button's or menu item's `kbd.key-hint`, `data-label` on stacked-table cells, `data-drag-over` on a drop zone while a file hovers, `data-pixel-paper-dragging` on the root while a sortable's item or a reel's pane is pressed or carried, `--reel-width`, `--reel-weight` and `--reel-anchor` on a reel's columns, slots and panes, `--rail` on `.railed` for a width the reader gave the rail, `data-reel-focus` on the control in a pane focus lands on, `data-tip` on glyph-only controls (the words the page's one tip shows), native `<dialog>` for overlays and native `[popover]` for the rail's drawer and tips. Behaviour the components own (board moves, a reel's moves, sizes and focus, a menu's placement and arrow keys, a context menu's placement and focus, tips, a toolbar's roving focus, the palette cursor, tab arrows, focus into dialogs, an error summary's focus, the flash, the remembered palette, the toast stack's hold and focus, a player's controls, the key help) is the adopter's to write.
1225
1231
 
1226
1232
  Palette: with no `data-scheme` the page follows `prefers-color-scheme`, live, in CSS alone. `Scheme.SchemeSwitch` is a Submodel with no service to provide: choosing a scheme returns its `PinScheme` Command, which sets or clears `data-scheme` on `<html>` (`"light"` for paper, `"dark"` for plum) and remembers the choice in `localStorage` (storage that refuses still switches the page; the choice is just not kept); `SchemeSwitch.select` does the same from outside the switch (a palette entry). `SchemeSwitch.init()` returns the switch on `"System"`, as the server renders it, with a `ReadScheme` Command the client runs after hydration, which reads the scheme `schemeScript` pinned before first paint; map its Commands into the application's `init`. Its Subscription follows `prefers-color-scheme` (Foldkit's `Dom.streamFromMediaQuery`), so the switch knows the palette `"System"` prints in. Read the choice with `SchemeSwitch.scheme` and the palette the page prints in, `"Paper"` or `"Plum"` (`Scheme.Palette`), with `SchemeSwitch.palette`: the pinned one, or under `"System"` the system's, followed as it changes (`"Paper"` until the browser reports, as the server renders). A scheme chosen in another tab is seen on that page's next load, not live. Put `schemeScript` inline in the page's `<head>` so a remembered palette applies before first paint. A subtree with `color-scheme: dark` prints plum colours inside a paper page, the dither too, which is how the specimen shows both palettes side by side.
1227
1233
 
@@ -0,0 +1,14 @@
1
+ import type { Html, HtmlBuilder } from "foldkit/html";
2
+ /** Configuration for rendering a bench with `view`. */
3
+ export type ViewConfig = Readonly<{
4
+ /** Above the work, at its own height: a PageHead, notices, and a Rail's open button first. */
5
+ head: ReadonlyArray<Html | string>;
6
+ /** The work, taking the rest of the window's height: a Reel, which fills it. */
7
+ body: ReadonlyArray<Html | string>;
8
+ }>;
9
+ /** A page whose work is the window: its head at its own height, then the work in all the
10
+ * height left, from under the top bar to the window's bottom and edge to edge (beside a Rail,
11
+ * from the rail's edge). The main landmark (`<main id="main">`). The page does not scroll;
12
+ * the work scrolls itself where it must (a reel's columns, sideways). */
13
+ export declare const view: <Message>(config: ViewConfig, h: HtmlBuilder<Message>) => Html;
14
+ //# sourceMappingURL=bench.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bench.d.ts","sourceRoot":"","sources":["../../../src/bench/bench.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAItD,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,8FAA8F;IAC9F,IAAI,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACnC,gFAAgF;IAChF,IAAI,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;CACpC,CAAC,CAAC;AAEH;;;0EAG0E;AAC1E,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAO3E,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
2
+ /** A page whose work is the window: its head at its own height, then the work in all the
3
+ * height left, from under the top bar to the window's bottom and edge to edge (beside a Rail,
4
+ * from the rail's edge). The main landmark (`<main id="main">`). The page does not scroll;
5
+ * the work scrolls itself where it must (a reel's columns, sideways). */
6
+ export const view = (config, h) => {
7
+ const { head, body } = config;
8
+ return __foldkitBrandViewResult((h.main([h.Id("main"), h.Class("bench")], [h.div([h.Class("bench-head")], head), h.div([h.Class("bench-work")], body)])), "@gjermundgaraba-designs/pixel-paper/src/bench/bench.js#view");
9
+ };
@@ -0,0 +1,3 @@
1
+ export { view } from "./bench.ts";
2
+ export type { ViewConfig } from "./bench.ts";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bench/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1 @@
1
+ export { view } from "./bench.js";
@@ -15,4 +15,14 @@ export type ViewConfig = Readonly<{
15
15
  }>;
16
16
  /** A state word in a 2px box. Its words are its meaning; the colour only repeats them. */
17
17
  export declare const view: <Message>(config: ViewConfig, h: HtmlBuilder<Message>) => Html;
18
+ /** Configuration for rendering a dot with `dotView`. */
19
+ export type DotConfig = Readonly<{
20
+ /** The state word: the dot's accessible name and its tip. */
21
+ label: string;
22
+ tone?: Tone;
23
+ }>;
24
+ /** A chip as a 10px cell in its tone, where a word would take the room a title needs (a task
25
+ * row's status, before its title). It is an image (`role="img"`) named by the word, which its
26
+ * tip shows, so the state never rests on its colour. */
27
+ export declare const dotView: <Message>(config: DotConfig, h: HtmlBuilder<Message>) => Html;
18
28
  //# sourceMappingURL=chip.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chip.d.ts","sourceRoot":"","sources":["../../../src/chip/chip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAItD;wEACwE;AACxE,eAAO,MAAM,IAAI,sEAA8D,CAAC;AAChF,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,CAAC;AAIpC,sDAAsD;AACtD,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ;8CAC0C;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC,CAAC;AAUH,0FAA0F;AAC1F,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAO3E,CAAC"}
1
+ {"version":3,"file":"chip.d.ts","sourceRoot":"","sources":["../../../src/chip/chip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAItD;wEACwE;AACxE,eAAO,MAAM,IAAI,sEAA8D,CAAC;AAChF,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,CAAC;AAIpC,sDAAsD;AACtD,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ;8CAC0C;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC,CAAC;AAUH,0FAA0F;AAC1F,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAO3E,CAAC;AAIF,wDAAwD;AACxD,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,CAAC;CACb,CAAC,CAAC;AAUH;;yDAEyD;AACzD,eAAO,MAAM,OAAO,GAAI,OAAO,UAAU,SAAS,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAS7E,CAAC"}
@@ -16,3 +16,22 @@ export const view = (config, h) => {
16
16
  const { label, tone = "Plain", isBusy = false } = config;
17
17
  return __foldkitBrandViewResult((h.span([...(isBusy ? [h.AriaBusy(true)] : []), h.Class(`chip${TONE_CLASS[tone]}`)], [label, ...(isBusy ? [h.span([h.Class("cursor"), h.AriaHidden(true)])] : [])])), "@gjermundgaraba-designs/pixel-paper/src/chip/chip.js#view");
18
18
  };
19
+ const DOT_CLASS = {
20
+ Plain: "chip-dot",
21
+ Go: "chip-dot go",
22
+ Warn: "chip-dot warn",
23
+ Blocked: "chip-dot blocked",
24
+ Done: "chip-dot done",
25
+ };
26
+ /** A chip as a 10px cell in its tone, where a word would take the room a title needs (a task
27
+ * row's status, before its title). It is an image (`role="img"`) named by the word, which its
28
+ * tip shows, so the state never rests on its colour. */
29
+ export const dotView = (config, h) => {
30
+ const { label, tone = "Plain" } = config;
31
+ return __foldkitBrandViewResult((h.span([
32
+ h.Role("img"),
33
+ h.AriaLabel(label),
34
+ h.DataAttribute("tip", label),
35
+ h.Class(DOT_CLASS[tone]),
36
+ ])), "@gjermundgaraba-designs/pixel-paper/src/chip/chip.js#dotView");
37
+ };
@@ -1,3 +1,3 @@
1
- export { view } from "./chip.ts";
2
- export type { Tone, ViewConfig } from "./chip.ts";
1
+ export { dotView, view } from "./chip.ts";
2
+ export type { DotConfig, Tone, ViewConfig } from "./chip.ts";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/chip/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/chip/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
@@ -1 +1 @@
1
- export { view } from "./chip.js";
1
+ export { dotView, view } from "./chip.js";
@@ -1,5 +1,6 @@
1
1
  import { DragAndDrop as UiDragAndDrop } from "@foldkit/ui";
2
2
  import { Option, Stream } from "effect";
3
+ import type { Attribute, HtmlBuilder } from "foldkit/html";
3
4
  /** Where a drag is: nothing pressed, pressed but not yet moved, carried by a pointer, or
4
5
  * carried by the keyboard. */
5
6
  type Phase = "Idle" | "Pressed" | "PointerDragging" | "KeyboardDragging";
@@ -24,6 +25,23 @@ export declare const escapeKeys: <Message>(message: Message) => Stream.Stream<Me
24
25
  /** The moves, release and cancel of the pointer `pointerId`, on the document; another
25
26
  * pointer's are not the drag's. */
26
27
  export declare const pressingPointer: (pointerId: number) => Stream.Stream<PointerEvent>;
28
+ /** What the pressing pointer `pointerId` does, as Messages: each move, the release and the
29
+ * browser's cancel. */
30
+ export declare const pointerMessages: <Message>(pointerId: number, toMessage: Readonly<{
31
+ moved: (event: PointerEvent) => Message;
32
+ released: (event: PointerEvent) => Message;
33
+ cancelled: () => Message;
34
+ }>) => Stream.Stream<Message>;
35
+ /** A splitter's handlers. The main button of a pointer presses it (`onPress`, while
36
+ * `isIdle`), and its arrows, unmodified, step it (`onStep`, one way or the other): the side
37
+ * arrows across a `"vertical"` splitter (it sizes a width), up and down along a
38
+ * `"horizontal"` one (a height). A modified arrow is the application's. */
39
+ export declare const splitterAttributes: <Message>(config: Readonly<{
40
+ orientation: "vertical" | "horizontal";
41
+ isIdle: boolean;
42
+ onPress: (pointerId: number, clientX: number, clientY: number) => Message;
43
+ onStep: (direction: -1 | 1) => Message;
44
+ }>, h: HtmlBuilder<Message>) => ReadonlyArray<Attribute<Message>>;
27
45
  /** Sizes the copy that follows the pointer to the element `sourceSelector` finds, and keeps the
28
46
  * pointer where it pressed that element. */
29
47
  export declare const measureLift: (element: Element, sourceSelector: string, pressX: number, pressY: number) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"drag.d.ts","sourceRoot":"","sources":["../../../src/drag/drag.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,IAAI,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAiB,MAAM,EAAS,MAAM,EAAQ,MAAM,QAAQ,CAAC;AAKpE;+BAC+B;AAC/B,KAAK,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAKzE,eAAO,MAAM,OAAO,gBAAiB,aAAa,CAAC,KAAK,KAAG,KAU1D,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,UAAU,UAAW,KAAK,KAAG,OACmB,CAAC;AAQ9D,eAAO,MAAM,gBAAgB,WACnB,UAAU,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,KACnE,aAAa,CAAC,OAE+B,CAAC;AAEjD,eAAO,MAAM,qBAAqB,WACxB,UAAU,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,KACxE,aAAa,CAAC,OAEoC,CAAC;AAEtD,eAAO,MAAM,YAAY,WACf,UAAU,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,KAC/D,aAAa,CAAC,OAE2B,CAAC;AAE7C,eAAO,MAAM,eAAe,QAAO,aAAa,CAAC,OAER,CAAC;AAE1C,eAAO,MAAM,aAAa,QAAO,aAAa,CAAC,OAER,CAAC;AAIxC,sDAAsD;AACtD,eAAO,MAAM,WAAW,IAAI,CAAC;AAE7B;4FAC4F;AAC5F,eAAO,MAAM,aAAa,mBAAoB,MAAM,KAAG,MASzC,CAAC;AAMf;4CAC4C;AAC5C,eAAO,MAAM,UAAU,GAAI,OAAO,SACzB,aAAa,WACX,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,OAAO,CAQvB,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,UAAU,GAAI,OAAO,WAAW,OAAO,KAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAMxE,CAAC;AAIL;oCACoC;AACpC,eAAO,MAAM,eAAe,cAAe,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAS3E,CAAC;AAIJ;6CAC6C;AAC7C,eAAO,MAAM,WAAW,YACb,OAAO,kBACA,MAAM,UACd,MAAM,UACN,MAAM,KACb,IAQF,CAAC;AAoBF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,cACd,OAAO,UACV,MAAM,WACL,MAAM,WACN,MAAM,KACd,MA+BF,CAAC;AAMF;;8DAE8D;AAC9D,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAK7C,CAAC"}
1
+ {"version":3,"file":"drag.d.ts","sourceRoot":"","sources":["../../../src/drag/drag.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,IAAI,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAiB,MAAM,EAAS,MAAM,EAAQ,MAAM,QAAQ,CAAC;AAEpE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAM3D;+BAC+B;AAC/B,KAAK,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAKzE,eAAO,MAAM,OAAO,gBAAiB,aAAa,CAAC,KAAK,KAAG,KAU1D,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,UAAU,UAAW,KAAK,KAAG,OACmB,CAAC;AAQ9D,eAAO,MAAM,gBAAgB,WACnB,UAAU,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,KACnE,aAAa,CAAC,OAE+B,CAAC;AAEjD,eAAO,MAAM,qBAAqB,WACxB,UAAU,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,KACxE,aAAa,CAAC,OAEoC,CAAC;AAEtD,eAAO,MAAM,YAAY,WACf,UAAU,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,KAC/D,aAAa,CAAC,OAE2B,CAAC;AAE7C,eAAO,MAAM,eAAe,QAAO,aAAa,CAAC,OAER,CAAC;AAE1C,eAAO,MAAM,aAAa,QAAO,aAAa,CAAC,OAER,CAAC;AAIxC,sDAAsD;AACtD,eAAO,MAAM,WAAW,IAAI,CAAC;AAE7B;4FAC4F;AAC5F,eAAO,MAAM,aAAa,mBAAoB,MAAM,KAAG,MASzC,CAAC;AAMf;4CAC4C;AAC5C,eAAO,MAAM,UAAU,GAAI,OAAO,SACzB,aAAa,WACX,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,OAAO,CAQvB,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,UAAU,GAAI,OAAO,WAAW,OAAO,KAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAMxE,CAAC;AAIL;oCACoC;AACpC,eAAO,MAAM,eAAe,cAAe,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAS3E,CAAC;AAEJ;wBACwB;AACxB,eAAO,MAAM,eAAe,GAAI,OAAO,aAC1B,MAAM,aACN,QAAQ,CAAC;IAClB,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IACxC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAC3C,SAAS,EAAE,MAAM,OAAO,CAAC;CAC1B,CAAC,KACD,MAAM,CAAC,MAAM,CAAC,OAAO,CASpB,CAAC;AAIL;;;4EAG4E;AAC5E,eAAO,MAAM,kBAAkB,GAAI,OAAO,UAChC,QAAQ,CAAC;IACf,WAAW,EAAE,UAAU,GAAG,YAAY,CAAC;IACvC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1E,MAAM,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC;CACxC,CAAC,KACC,WAAW,CAAC,OAAO,CAAC,KACtB,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAmBlC,CAAC;AAIF;6CAC6C;AAC7C,eAAO,MAAM,WAAW,YACb,OAAO,kBACA,MAAM,UACd,MAAM,UACN,MAAM,KACb,IAQF,CAAC;AAoBF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,cACd,OAAO,UACV,MAAM,WACL,MAAM,WACN,MAAM,KACd,MA+BF,CAAC;AAMF;;8DAE8D;AAC9D,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAK7C,CAAC"}
@@ -2,10 +2,12 @@ import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
2
2
  // Shared by Board and Sortable, the two users of @foldkit/ui's DragAndDrop, and Reel, which
3
3
  // holds its own: the phase a drag is in, the DragAndDrop Messages both build, what a press
4
4
  // lifts, Escape taken before anything else, the pressing pointer's events, the lifted copy's
5
- // measure, and the closest-centre drop target a sortable resolves for a pointer.
5
+ // measure, and the closest-centre drop target a sortable resolves for a pointer. A splitter's
6
+ // handlers are the Reel's and the Rail's.
6
7
  import { DragAndDrop as UiDragAndDrop } from "@foldkit/ui";
7
8
  import { Array, Effect, Option, Order, Stream, pipe } from "effect";
8
9
  import * as Dom from "foldkit/dom";
10
+ import { isPlain } from "../keys/keys.js";
9
11
  // NOTE: @foldkit/ui gap: DragAndDrop has no accessor for which input holds an item.
10
12
  // `ghostStyle` is Some only while a pointer drags, so it tells a pointer drag from a keyboard
11
13
  // one.
@@ -83,6 +85,32 @@ export const escapeKeys = (message) => __foldkitBrandViewResult((Dom.streamFromE
83
85
  /** The moves, release and cancel of the pointer `pointerId`, on the document; another
84
86
  * pointer's are not the drag's. */
85
87
  export const pressingPointer = (pointerId) => __foldkitBrandViewResult((Stream.filter(Stream.mergeAll(Array.map(["pointermove", "pointerup", "pointercancel"], (type) => __foldkitBrandViewResult((Stream.fromEventListener(document, type)), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous")), { concurrency: "unbounded" }), (event) => __foldkitBrandViewResult((event.pointerId === pointerId), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~2"))), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#pressingPointer");
88
+ /** What the pressing pointer `pointerId` does, as Messages: each move, the release and the
89
+ * browser's cancel. */
90
+ export const pointerMessages = (pointerId, toMessage) => __foldkitBrandViewResult((Stream.map(pressingPointer(pointerId), (event) => {
91
+ if (event.type === "pointermove") {
92
+ return __foldkitBrandViewResult((toMessage.moved(event)), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~3");
93
+ }
94
+ else if (event.type === "pointerup") {
95
+ return __foldkitBrandViewResult((toMessage.released(event)), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~3");
96
+ }
97
+ else {
98
+ return __foldkitBrandViewResult((toMessage.cancelled()), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~3");
99
+ }
100
+ })), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#pointerMessages");
101
+ // SPLITTER
102
+ /** A splitter's handlers. The main button of a pointer presses it (`onPress`, while
103
+ * `isIdle`), and its arrows, unmodified, step it (`onStep`, one way or the other): the side
104
+ * arrows across a `"vertical"` splitter (it sizes a width), up and down along a
105
+ * `"horizontal"` one (a height). A modified arrow is the application's. */
106
+ export const splitterAttributes = (config, h) => {
107
+ const [less, more] = config.orientation === "vertical" ? ["ArrowLeft", "ArrowRight"] : ["ArrowUp", "ArrowDown"];
108
+ return __foldkitBrandViewResult(([
109
+ h.AriaOrientation(config.orientation),
110
+ h.OnPointerDown((_pointerType, button, _screenX, _screenY, _time, clientX, clientY, pointerId) => __foldkitBrandViewResult((Option.liftPredicate(config.onPress(pointerId, clientX, clientY), () => __foldkitBrandViewResult((button === LEFT_BUTTON && config.isIdle), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~5"))), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~4")),
111
+ h.OnKeyDownPreventDefault((key, modifiers) => __foldkitBrandViewResult((Option.liftPredicate(config.onStep(key === more ? 1 : -1), () => __foldkitBrandViewResult((isPlain(modifiers) && (key === less || key === more)), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~7"))), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~6")),
112
+ ]), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#splitterAttributes");
113
+ };
86
114
  // LIFTED COPY
87
115
  /** Sizes the copy that follows the pointer to the element `sourceSelector` finds, and keeps the
88
116
  * pointer where it pressed that element. */
@@ -114,14 +142,14 @@ const isOnSameRow = (box, other) => __foldkitBrandViewResult((Math.abs(other.top
114
142
  * in a column of rows it is read down, so one reading serves a list and a grid.
115
143
  */
116
144
  export const insertionIndex = (container, heldId, clientX, clientY) => {
117
- const items = Array.map(Array.fromIterable(container.querySelectorAll("[data-sortable-id]")), (item) => (__foldkitBrandViewResult(({ id: item.dataset["sortableId"], box: boxOf(item) }), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~3")));
145
+ const items = Array.map(Array.fromIterable(container.querySelectorAll("[data-sortable-id]")), (item) => (__foldkitBrandViewResult(({ id: item.dataset["sortableId"], box: boxOf(item) }), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~8")));
118
146
  // read with the held item in its place: two tiles side by side are a row even while one of
119
147
  // them is held
120
- const isTiled = Array.some(items, ({ box }, index) => __foldkitBrandViewResult((Option.exists(Array.get(items, index + 1), (next) => __foldkitBrandViewResult((isOnSameRow(box, next.box)), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~5"))), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~4"));
121
- const boxes = pipe(Array.filter(items, (item) => __foldkitBrandViewResult((item.id !== heldId), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~6")), Array.map(({ box }) => __foldkitBrandViewResult((box), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~7")));
148
+ const isTiled = Array.some(items, ({ box }, index) => __foldkitBrandViewResult((Option.exists(Array.get(items, index + 1), (next) => __foldkitBrandViewResult((isOnSameRow(box, next.box)), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~10"))), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~9"));
149
+ const boxes = pipe(Array.filter(items, (item) => __foldkitBrandViewResult((item.id !== heldId), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~11")), Array.map(({ box }) => __foldkitBrandViewResult((box), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~12")));
122
150
  const distance = (box) => __foldkitBrandViewResult((Math.hypot(box.centreX - clientX, box.centreY - clientY)), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#distance");
123
- const byDistance = Order.mapInput(Order.Number, ({ box }) => __foldkitBrandViewResult((distance(box)), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~8"));
124
- return __foldkitBrandViewResult((Array.match(Array.map(boxes, (box, index) => (__foldkitBrandViewResult(({ box, index }), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~9"))), {
151
+ const byDistance = Order.mapInput(Order.Number, ({ box }) => __foldkitBrandViewResult((distance(box)), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~13"));
152
+ return __foldkitBrandViewResult((Array.match(Array.map(boxes, (box, index) => (__foldkitBrandViewResult(({ box, index }), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~14"))), {
125
153
  onEmpty: () => __foldkitBrandViewResult((0), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#onEmpty"),
126
154
  onNonEmpty: (candidates) => {
127
155
  const { box, index } = Array.min(candidates, byDistance);
@@ -135,4 +163,4 @@ const DRAGGING_ATTRIBUTE = "data-pixel-paper-dragging";
135
163
  /** While a pointer presses or carries an item, the page is marked
136
164
  * (`data-pixel-paper-dragging` on the root), so the stylesheet prints the grabbing cursor and
137
165
  * holds text selection, with no style element of its own. */
138
- export const markDragging = Stream.callback(() => __foldkitBrandViewResult((Effect.acquireRelease(Effect.sync(() => __foldkitBrandViewResult((document.documentElement.setAttribute(DRAGGING_ATTRIBUTE, "")), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~11")), () => __foldkitBrandViewResult((Effect.sync(() => __foldkitBrandViewResult((document.documentElement.removeAttribute(DRAGGING_ATTRIBUTE)), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~13"))), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~12")).pipe(Effect.flatMap(() => __foldkitBrandViewResult((Effect.never), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~14")))), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~10"));
166
+ export const markDragging = Stream.callback(() => __foldkitBrandViewResult((Effect.acquireRelease(Effect.sync(() => __foldkitBrandViewResult((document.documentElement.setAttribute(DRAGGING_ATTRIBUTE, "")), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~16")), () => __foldkitBrandViewResult((Effect.sync(() => __foldkitBrandViewResult((document.documentElement.removeAttribute(DRAGGING_ATTRIBUTE)), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~18"))), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~17")).pipe(Effect.flatMap(() => __foldkitBrandViewResult((Effect.never), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~19")))), "@gjermundgaraba-designs/pixel-paper/src/drag/drag.js#anonymous~15"));
@@ -24,6 +24,9 @@ export type ViewConfig<Message> = Readonly<{
24
24
  isBoxed?: boolean;
25
25
  /** Code and identifiers: the system monospace. */
26
26
  isMono?: boolean;
27
+ /** The label is for screen readers only (`.sr-only`): a field whose place says what it is,
28
+ * such as a browser's address bar. It still names the input. */
29
+ isLabelHidden?: boolean;
27
30
  /** The input's type (`"date"` for the browser's date control, always boxed); text when not
28
31
  * given. */
29
32
  type?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../../src/field/field.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAM9D,wFAAwF;AACxF,eAAO,MAAM,SAAS,GAAI,OAAO,MAC3B,MAAM,SACH,MAAM,GAAG,SAAS,KACtB,WAAW,CAAC,OAAO,CAAC,KACtB,aAAa,CAAC,IAAI,CACmE,CAAC;AAEzF;;+EAE+E;AAC/E,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAClC,MAAM,SACH,MAAM,GAAG,SAAS,WAChB,OAAO,KACb,WAAW,CAAC,OAAO,CAAC,KACtB,aAAa,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAGqD,CAAC;AAIjG,4DAA4D;AAC5D,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACpC,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;uCAEmC;IACnC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,yFAAyF;IACzF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;iBACa;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;iBACa;IACb,eAAe,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;CACvC,CAAC,CAAC;AAEH;;;6EAG6E;AAC7E,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IA4CpF,CAAC"}
1
+ {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../../src/field/field.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAM9D,wFAAwF;AACxF,eAAO,MAAM,SAAS,GAAI,OAAO,MAC3B,MAAM,SACH,MAAM,GAAG,SAAS,KACtB,WAAW,CAAC,OAAO,CAAC,KACtB,aAAa,CAAC,IAAI,CACmE,CAAC;AAEzF;;+EAE+E;AAC/E,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAClC,MAAM,SACH,MAAM,GAAG,SAAS,WAChB,OAAO,KACb,WAAW,CAAC,OAAO,CAAC,KACtB,aAAa,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAGqD,CAAC;AAIjG,4DAA4D;AAC5D,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACpC,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;uCAEmC;IACnC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,yFAAyF;IACzF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;qEACiE;IACjE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;iBACa;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;iBACa;IACb,eAAe,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;CACvC,CAAC,CAAC;AAEH;;;6EAG6E;AAC7E,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IA6CpF,CAAC"}
@@ -15,7 +15,7 @@ export const describedByError = (id, error, hasHelp, h) => __foldkitBrandViewRes
15
15
  * `aria-invalid` and describes it before the help, and `isRequired` sets `aria-required`.
16
16
  * The input is boxless, a dotted baseline that turns solid ink on focus. */
17
17
  export const view = (config, h) => {
18
- const { id, label, value, onInput, help, error, isRequired = false, isBoxed = false, isMono = false, type, inputAttributes = [], } = config;
18
+ const { id, label, value, onInput, help, error, isRequired = false, isBoxed = false, isMono = false, isLabelHidden = false, type, inputAttributes = [], } = config;
19
19
  return __foldkitBrandViewResult((UiInput.view({
20
20
  id,
21
21
  value,
@@ -24,7 +24,7 @@ export const view = (config, h) => {
24
24
  ...(type !== undefined && { type }),
25
25
  hasDescription: help !== undefined,
26
26
  toView: (groups) => __foldkitBrandViewResult((h.div([h.Class(`field${isBoxed || type === "date" ? " boxed" : ""}${isMono ? " mono" : ""}`)], [
27
- h.label(groups.label, [label]),
27
+ h.label([...groups.label, ...(isLabelHidden ? [h.Class("sr-only")] : [])], [label]),
28
28
  // NOTE: @foldkit/ui gap: Input has no required option, so the wrapper says it.
29
29
  h.input([
30
30
  ...inputAttributes,
@@ -2,6 +2,7 @@ export * as Announcer from "./announcer/index.ts";
2
2
  export * as AudioFrame from "./audioFrame/index.ts";
3
3
  export * as Banner from "./banner/index.ts";
4
4
  export * as BarChart from "./barChart/index.ts";
5
+ export * as Bench from "./bench/index.ts";
5
6
  export * as Board from "./board/index.ts";
6
7
  export * as Button from "./button/index.ts";
7
8
  export * as Card from "./card/index.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
package/dist/src/index.js CHANGED
@@ -2,6 +2,7 @@ export * as Announcer from "./announcer/index.js";
2
2
  export * as AudioFrame from "./audioFrame/index.js";
3
3
  export * as Banner from "./banner/index.js";
4
4
  export * as BarChart from "./barChart/index.js";
5
+ export * as Bench from "./bench/index.js";
5
6
  export * as Board from "./board/index.js";
6
7
  export * as Button from "./button/index.js";
7
8
  export * as Card from "./card/index.js";
@@ -1,3 +1,5 @@
1
1
  export { patchView, view } from "./lineDiff.ts";
2
2
  export type { PatchViewConfig, ViewConfig } from "./lineDiff.ts";
3
+ export { describePatch } from "./patch.ts";
4
+ export type { PatchDescription } from "./patch.ts";
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lineDiff/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lineDiff/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
@@ -1 +1,2 @@
1
1
  export { patchView, view } from "./lineDiff.js";
2
+ export { describePatch } from "./patch.js";