@gjermundgaraba-designs/pixel-paper 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +29 -22
- package/dist/src/announcer/announcer.d.ts +34 -26
- package/dist/src/announcer/announcer.d.ts.map +1 -1
- package/dist/src/announcer/announcer.js +42 -32
- package/dist/src/announcer/index.d.ts +1 -1
- package/dist/src/announcer/index.d.ts.map +1 -1
- package/dist/src/announcer/index.js +1 -1
- package/dist/src/banner/banner.d.ts +1 -1
- package/dist/src/banner/banner.js +1 -1
- package/dist/src/barChart/barChart.js +1 -1
- package/dist/src/board/board.d.ts +1 -4
- package/dist/src/board/board.d.ts.map +1 -1
- package/dist/src/board/board.js +8 -71
- package/dist/src/button/button.d.ts +1 -1
- package/dist/src/button/button.js +1 -1
- package/dist/src/card/card.d.ts +35 -9
- package/dist/src/card/card.d.ts.map +1 -1
- package/dist/src/card/card.js +46 -8
- package/dist/src/card/index.d.ts +2 -2
- package/dist/src/card/index.d.ts.map +1 -1
- package/dist/src/card/index.js +1 -1
- package/dist/src/commandPalette/commandPalette.d.ts +4 -0
- package/dist/src/commandPalette/commandPalette.d.ts.map +1 -1
- package/dist/src/confirmDialog/confirmDialog.d.ts +18 -26
- package/dist/src/confirmDialog/confirmDialog.d.ts.map +1 -1
- package/dist/src/confirmDialog/confirmDialog.js +21 -19
- package/dist/src/confirmDialog/index.d.ts +1 -1
- package/dist/src/confirmDialog/index.d.ts.map +1 -1
- package/dist/src/confirmDialog/index.js +1 -1
- package/dist/src/contextMenu/contextMenu.d.ts +6 -6
- package/dist/src/dialog/dialog.d.ts +36 -7
- package/dist/src/dialog/dialog.d.ts.map +1 -1
- package/dist/src/dialog/dialog.js +32 -12
- package/dist/src/dialog/index.d.ts +1 -1
- package/dist/src/dialog/index.d.ts.map +1 -1
- package/dist/src/dialog/index.js +1 -1
- package/dist/src/drag/drag.d.ts +40 -0
- package/dist/src/drag/drag.d.ts.map +1 -0
- package/dist/src/drag/drag.js +134 -0
- package/dist/src/emptyState/emptyState.d.ts +6 -4
- package/dist/src/emptyState/emptyState.d.ts.map +1 -1
- package/dist/src/emptyState/emptyState.js +1 -1
- package/dist/src/errorSummary/errorSummary.d.ts +1 -1
- package/dist/src/errorSummary/errorSummary.js +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/keyHelp/keyHelp.d.ts +4 -0
- package/dist/src/keyHelp/keyHelp.d.ts.map +1 -1
- package/dist/src/lineChart/lineChart.js +1 -1
- package/dist/src/liveRegion/index.d.ts +3 -0
- package/dist/src/liveRegion/index.d.ts.map +1 -0
- package/dist/src/liveRegion/index.js +1 -0
- package/dist/src/liveRegion/liveRegion.d.ts +42 -0
- package/dist/src/liveRegion/liveRegion.d.ts.map +1 -0
- package/dist/src/liveRegion/liveRegion.js +35 -0
- package/dist/src/loading/loading.d.ts +8 -3
- package/dist/src/loading/loading.d.ts.map +1 -1
- package/dist/src/loading/loading.js +4 -3
- package/dist/src/menu/menu.d.ts +6 -6
- package/dist/src/meter/meter.d.ts +3 -0
- package/dist/src/meter/meter.d.ts.map +1 -1
- package/dist/src/meter/meter.js +6 -3
- package/dist/src/remote/index.d.ts +3 -0
- package/dist/src/remote/index.d.ts.map +1 -0
- package/dist/src/remote/index.js +1 -0
- package/dist/src/remote/remote.d.ts +29 -0
- package/dist/src/remote/remote.d.ts.map +1 -0
- package/dist/src/remote/remote.js +41 -0
- package/dist/src/skipLink/index.d.ts +1 -1
- package/dist/src/skipLink/index.d.ts.map +1 -1
- package/dist/src/skipLink/index.js +1 -1
- package/dist/src/skipLink/skipLink.d.ts +24 -8
- package/dist/src/skipLink/skipLink.d.ts.map +1 -1
- package/dist/src/skipLink/skipLink.js +27 -8
- package/dist/src/sortable/index.d.ts +3 -0
- package/dist/src/sortable/index.d.ts.map +1 -0
- package/dist/src/sortable/index.js +1 -0
- package/dist/src/sortable/sortable.d.ts +1144 -0
- package/dist/src/sortable/sortable.d.ts.map +1 -0
- package/dist/src/sortable/sortable.js +504 -0
- package/dist/src/toast/index.d.ts +1 -1
- package/dist/src/toast/index.d.ts.map +1 -1
- package/dist/src/toast/index.js +1 -1
- package/dist/src/toast/toast.d.ts +32 -20
- package/dist/src/toast/toast.d.ts.map +1 -1
- package/dist/src/toast/toast.js +8 -3
- package/dist/src/topBar/barHeight.d.ts +6 -0
- package/dist/src/topBar/barHeight.d.ts.map +1 -1
- package/dist/src/topBar/barHeight.js +4 -0
- package/dist/src/topBar/index.d.ts +1 -1
- package/dist/src/topBar/index.d.ts.map +1 -1
- package/dist/src/topBar/index.js +1 -1
- package/dist/src/topBar/topBar.d.ts +1 -2
- package/dist/src/topBar/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/announcer/announcer.ts +48 -32
- package/src/announcer/index.ts +1 -1
- package/src/banner/banner.ts +1 -1
- package/src/barChart/barChart.ts +1 -1
- package/src/board/board.ts +18 -87
- package/src/button/button.ts +1 -1
- package/src/card/card.ts +120 -21
- package/src/card/index.ts +2 -2
- package/src/confirmDialog/confirmDialog.ts +25 -32
- package/src/confirmDialog/index.ts +2 -0
- package/src/dialog/dialog.ts +39 -13
- package/src/dialog/index.ts +3 -1
- package/src/drag/drag.ts +207 -0
- package/src/emptyState/emptyState.ts +7 -5
- package/src/errorSummary/errorSummary.ts +1 -1
- package/src/index.ts +3 -0
- package/src/lineChart/lineChart.ts +1 -1
- package/src/liveRegion/index.ts +2 -0
- package/src/liveRegion/liveRegion.ts +75 -0
- package/src/loading/loading.ts +11 -5
- package/src/meter/meter.ts +17 -3
- package/src/pixel-paper.css +182 -27
- package/src/remote/index.ts +2 -0
- package/src/remote/remote.ts +76 -0
- package/src/skipLink/index.ts +1 -1
- package/src/skipLink/skipLink.ts +38 -9
- package/src/sortable/index.ts +14 -0
- package/src/sortable/sortable.ts +857 -0
- package/src/toast/index.ts +1 -0
- package/src/toast/toast.ts +9 -3
- package/src/tokens.css +13 -11
- package/src/topBar/barHeight.ts +6 -0
- package/src/topBar/index.ts +1 -1
- package/src/topBar/topBar.ts +1 -2
package/DESIGN.md
CHANGED
|
@@ -890,7 +890,7 @@ The Deck is one centred column, `max-width: 1160px` (`--deck`), padded 24px top,
|
|
|
890
890
|
|
|
891
891
|
**Space between components:** three steps and nothing else, given by Stack (a column whose children take its width; `m` by default), Group (one line centred on its cross axis that never wraps; `s`) and Wrap (a line of buttons or chips that wraps, the same gap across and down; `s`). `--space-s` (8px) is for things in one line, `--space-m` (14px) for the parts of one body (a panel's, a card's and a window's body grid use it too), `--space-l` (`--gap`, 24px) for whole surfaces. They draw nothing: no paper, border or shadow, and their children keep `min-width: 0` so a wide table scrolls inside its own box.
|
|
892
892
|
|
|
893
|
-
**Top bar:** sticky (z-index 40), Paper White, closed below by a 2px ink rule, padded 8px 20px, wrapping with a 10px × 16px gap; the right-hand tools wrap as one group. Anchors land 16px clear of it at whatever height it wraps to: the page's first TopBar keeps `--topbar-h` on the page at its measured height (a bar shown inside the page leaves it alone), and `html`'s `scroll-padding-top` adds 16px to it (to one line's 48px until the bar is measured). Under 600px the wordmark and tools share the first line, padded 8px 12px, and the nav drops to a full-width strip under them with equal segments; where wordmark and tools do not fit side by side the tools wrap under the wordmark, which never clips.
|
|
893
|
+
**Top bar:** sticky (z-index 40), Paper White, closed below by a 2px ink rule, padded 8px 20px, wrapping with a 10px × 16px gap; the right-hand tools wrap as one group, and a group wider than the window wraps onto lines of its own at the right, `8px` apart. Anchors land 16px clear of it at whatever height it wraps to: the page's first TopBar keeps `--topbar-h` on the page at its measured height (a bar shown inside the page leaves it alone), and `html`'s `scroll-padding-top` adds 16px to it (to one line's 48px until the bar is measured). Under 600px the wordmark and tools share the first line, padded 8px 12px, and the nav drops to a full-width strip under them with equal segments; where wordmark and tools do not fit side by side the tools wrap under the wordmark, which never clips. Every tool stays, text buttons and the connection word included: a tool in the bar (sign out) may be the only way to its action, so an application that wants a shorter bar on a phone chooses what to leave out itself.
|
|
894
894
|
|
|
895
895
|
**Rows under 600px:** hover tools hide; titles wrap to two lines (three in an outline, with chips on the line below) instead of clipping beside the chips. Inline choices wrap onto the next line with a 2px × 24px gap rather than running off the panel.
|
|
896
896
|
|
|
@@ -914,7 +914,7 @@ Frames with no surface of their own (Deck, Wall, Stage) are described here rathe
|
|
|
914
914
|
|
|
915
915
|
Dialogs are native `<dialog>` elements that fill the viewport as a veil; the card centres in a 24px pad (or sits at 18vh for the command palette) at `min(680px, 100%)`, or `min(440px, 100%)` when narrow, capped at `calc(100vh - 48px)` with its body scrolling between a fixed title bar and a fixed action row. The Banner is fixed 16px above the bottom edge, centred, at most `min(680px, 100vw - 32px)`.
|
|
916
916
|
|
|
917
|
-
Breakpoints: 600px (phone; the page title steps to 18px, stats two across, the wall's corner joins the flow, toasts span the bottom),
|
|
917
|
+
Breakpoints: 600px (phone; the page title steps to 18px, stats two across, the wall's corner joins the flow, toasts span the bottom), 900px (board columns stack, above it they stretch; a stat's figure steps to 22px).
|
|
918
918
|
|
|
919
919
|
### Named Rules
|
|
920
920
|
|
|
@@ -996,7 +996,7 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
|
|
|
996
996
|
- **Shape:** square, 2px ink border, `padding: 5px 14px`, control register on one line (a button's words never wrap), inline-flex with a 6px gap for a leading glyph. Small: `3px 10px`, 11px, 2px shadow.
|
|
997
997
|
- **Default:** Paper White fill, Printer Ink text, button shadow. **Primary:** Printer Ink fill, Paper White text. **Danger:** paper fill, Urgent Red text and border, danger shadow; its keyboard ring turns red. Used only as the commit inside a ConfirmDialog; a destructive action at rest is a neutral button that opens a ConfirmDialog with `isDanger`.
|
|
998
998
|
- **Active:** `translate(2px, 2px)` into a 1px shadow. **Keyboard:** the global ring, 2px solid ink offset 2px.
|
|
999
|
-
- **Disabled / held:** Muted Ink text and border (a done button inverts instead, below), no shadow, no travel. One held rule covers `isBusy`, a done `done` and `isDisabled` (which means what it means in `@foldkit/ui`): the button renders `aria-disabled="true"`, stays focusable, has no click handler, and renders `type="button"`, so a held submit button submits nothing by itself. Enter in a field can still submit its form, so the form's update ignores a submit while its work is saving. A busy button says so, `aria-busy="true"`, and its blinking `span.cursor` prints in the right padding one cell after the label (5×9px on a small button), so going busy moves nothing and the button keeps its label and its width. A LinkButton is never busy. Give the button an id (`attributes`) for `Announcer.
|
|
999
|
+
- **Disabled / held:** Muted Ink text and border (a done button inverts instead, below), no shadow, no travel. One held rule covers `isBusy`, a done `done` and `isDisabled` (which means what it means in `@foldkit/ui`): the button renders `aria-disabled="true"`, stays focusable, has no click handler, and renders `type="button"`, so a held submit button submits nothing by itself. Enter in a field can still submit its form, so the form's update ignores a submit while its work is saving. A busy button says so, `aria-busy="true"`, and its blinking `span.cursor` prints in the right padding one cell after the label (5×9px on a small button), so going busy moves nothing and the button keeps its label and its width. A LinkButton is never busy. Give the button an id (`attributes`) for `Announcer.Flash`. A view takes `attributes`, or a named part's `<part>Attributes`, only where a caller passes them (Building components, rule 4); they never carry a class.
|
|
1000
1000
|
- **Done** (`done`, with a `glyph`): the work went (a form sent). `button.btn.done[aria-disabled="true"]` prints in Printer Ink with paper text, flat and unavailable, the check in its glyph's place: success is not a status colour and never green. The label and the done label share one cell (`span.btn-words`), the hidden word keeping its room, so the button is as wide as the longer word and never changes width. Flash and announce it where it is set.
|
|
1001
1001
|
- **Default ring (`isDefault`):** the action Enter takes in a card; one ring at a time across the action row. A held default that does not hold focus (a confirm's cancel while its delete runs) prints as any held button, with no ring and no shadow; its 4px margin stays, so nothing moves.
|
|
1002
1002
|
- **Toggle:** a button that stays pressed (a tool in force, a filter) takes `aria-pressed` from `Button.pressedAttribute(isPressed, h)` in its `attributes`, the one source of it for every pressable in the system.
|
|
@@ -1011,7 +1011,7 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
|
|
|
1011
1011
|
- **Crumbs** → `nav.crumbs > ol > li`: the path in the 11px pixel face, muted slashes between, the last crumb ink with `aria-current="page"`, the others muted with a dotted underline on hover.
|
|
1012
1012
|
- **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.
|
|
1013
1013
|
- **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.
|
|
1014
|
-
- **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`.
|
|
1014
|
+
- **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`.
|
|
1015
1015
|
- **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.
|
|
1016
1016
|
|
|
1017
1017
|
### Chips
|
|
@@ -1039,13 +1039,14 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
|
|
|
1039
1039
|
|
|
1040
1040
|
- **Panel** → `section.panel[aria-labelledby] > header.panel-tb > h2 + span.meta`, then `div.panel-body` (or the body directly when `isFlush`), then `div.panel-foot`. The section 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. 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%.
|
|
1041
1041
|
- **Deck** → `main.deck#main`: the column of panels.
|
|
1042
|
-
- **EmptyState** → `div.empty >
|
|
1042
|
+
- **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.
|
|
1043
1043
|
- **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).
|
|
1044
|
-
- **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 `Announcer.
|
|
1044
|
+
- **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.
|
|
1045
1045
|
- **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.
|
|
1046
1046
|
- **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.
|
|
1047
1047
|
- **Window** → `section.panel.window (.flush) > header.panel-tb + div.window-body + div.panel-foot`: a Panel for a stage, 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`), centred, with 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.
|
|
1048
|
-
- **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.
|
|
1048
|
+
- **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).
|
|
1049
|
+
- **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.
|
|
1049
1050
|
- **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.
|
|
1050
1051
|
|
|
1051
1052
|
### Outcomes
|
|
@@ -1054,11 +1055,11 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
|
|
|
1054
1055
|
|
|
1055
1056
|
### Cards (dialogs)
|
|
1056
1057
|
|
|
1057
|
-
**Dialog** is a Submodel over `@foldkit/ui`'s Dialog: a `<dialog class="veil">` that fills the viewport, opened with `show()` in place rather than `showModal()` in the top layer. That is a deliberate exception to the baseline's "prefer the platform" for overlays: Foldkit's Dialog (through `Dom.showDialog`) makes everything outside the dialog `inert` and `aria-hidden`, traps focus, handles Escape on the topmost dialog and returns focus on close, so the platform's modal behaviour is reproduced, not dropped; what is lost is the top layer and its `::backdrop`. The veil is therefore an element of its own, `div.veil-backdrop` (fixed, Veil with the ink checker grain, entering in 3 steps over 0.12s), under `div.veil-pad` (`.top` for the palette), which lets a press through everywhere but on the card it holds; a press on the veil, Escape, or a control given the `closeButton` group closes it and reports `Closed`. Opening and closing are the parent's, through `Dialog.open` and `Dialog.close` in its update, and a step the parent asked for reports nothing; `Dialog.foldDialog` lifts a dialog the parent always holds (its update, `open` and `close`) into the parent at once, `Closed` reported only where the parent maps it. The content is the parent's `toView`, which receives the attribute groups to spread (`title`, `description`, `initialFocus`, `closeButton`, `actionBar`). Focus starts on the element given the `initialFocus` group, else the first control. The Card inside names the dialog through its title (the `title` group, `aria-labelledby`); a dialog without a heading takes `label`, printed for screen readers only. `isAlert` makes it an `alertdialog`, and `hasDescription` points `aria-describedby` at the element given the `description` group. Because the page outside is inert and hidden, anything that must be heard or reached while it is open renders inside it: the topmost open dialog hosts the page's live region and the toast stack, both outside the card, through its `toLiveRegionView` (see Building components).
|
|
1058
|
+
**Dialog** is a Submodel over `@foldkit/ui`'s Dialog: a `<dialog class="veil">` that fills the viewport, opened with `show()` in place rather than `showModal()` in the top layer. That is a deliberate exception to the baseline's "prefer the platform" for overlays: Foldkit's Dialog (through `Dom.showDialog`) makes everything outside the dialog `inert` and `aria-hidden`, traps focus, handles Escape on the topmost dialog and returns focus on close, so the platform's modal behaviour is reproduced, not dropped; what is lost is the top layer and its `::backdrop`. The veil is therefore an element of its own, `div.veil-backdrop` (fixed, Veil with the ink checker grain, entering in 3 steps over 0.12s), under `div.veil-pad` (`.top` for the palette), which lets a press through everywhere but on the card it holds; a press on the veil, Escape, or a control given the `closeButton` group closes it and reports `Closed`. While its work must land in it (a save whose failure the reader has to see), the update that starts the work holds those three in the dialog's Model (`Dialog.setCloseHeld`), so its update leaves them out, however soon after the start they come: it stays open and reports no `Closed`. The update where the work fails lets go (`Dialog.setCloseHeld(model, false)`), or the reader is shut in with the failure; `Dialog.close` closes it and lets go of the hold. Opening and closing are the parent's, through `Dialog.open` and `Dialog.close` in its update, and a step the parent asked for reports nothing; `Dialog.foldDialog` lifts a dialog the parent always holds (its update, `open` and `close`) into the parent at once, `Closed` reported only where the parent maps it. The content is the parent's `toView`, which receives the attribute groups to spread (`title`, `description`, `initialFocus`, `closeButton`, `actionBar`). Focus starts on the element given the `initialFocus` group, else the first control. The Card inside names the dialog through its title (the `title` group, `aria-labelledby`); a dialog without a heading takes `label`, printed for screen readers only. `isAlert` makes it an `alertdialog`, and `hasDescription` points `aria-describedby` at the element given the `description` group. Because the page outside is inert and hidden, anything that must be heard or reached while it is open renders inside it: the topmost open dialog hosts the page's live region and the toast stack, both outside the card, through its `toLiveRegionView` (see Building components).
|
|
1058
1059
|
|
|
1059
|
-
**Card** → `div.card` (`.narrow`;
|
|
1060
|
+
**Card** → `div.card` (`.narrow`; `form.card` from `Card.formView`, named by its title (`aria-label`), so a form landmark; no role of its own, the Dialog around it is the dialog): a paper panel with the card shadow, `min(680px, 100%)` (440px narrow), entering in 3 steps over 0.14s from `scale(0.97)`. An inverted title bar `header.card-tb` (`6px 8px 6px 12px`, 13px uppercase 0.06em (`--tracking-head`), a close IconButton when it has `closeAttributes`, held by `Card.formView`'s `isBusy` while work the reader may not walk away from runs: `aria-disabled`, in the keyboard, its `closeAttributes` and so its handler dropped, faded to 0.6 on the bar, as a held button in a panel's title bar), a scrolling `div.card-body` (16px, 14px gap; a word longer than the card, a path or an id, breaks rather than widening it), and `div.card-actions` (`12px 16px`) over a 2px ink rule: `lead` first, then `span.card-acts` holding the actions at the right, which drop below the lead together when the bar wraps. Inside a Dialog, where the page's keys wait, the bar moves focus between its actions: `h` or ← to the one before, `l` or → to the one after, in the order they print, stopping at the ends, never pressing one (Enter, Space and Tab stay the browser's). The keys are bound by a Mount on the bar (`BindActionKeys`), which reads the real key event: only a key whose target is one of that bar's own buttons or links, unmodified, outside an IME and not already handled, and only when there is an action that way, moves focus and has its default prevented. A field, the title bar's close, a menu's items and a nested dialog's bar keep their keys. A disabled action is passed over; an `aria-disabled` one (held: busy, done or disabled) is still a stop and still ignores presses. **Card.formView** is the one way to make a form card: its `body` in one Fields group, then the action bar's cancel (`cancel`, closing through `closeAttributes`) and the primary default submit button (`submit`, its id `Card.submitId(id)`). One flag, `isBusy`, holds every way the form could change or leave while its work runs: the submit button prints busy and turns `type="button"`, the close glyph and cancel are held with their close attributes dropped, and the Fields group is natively disabled and `aria-busy`, so nothing changes under the send and nothing sends twice. In a Dialog, the update that sends also holds the Dialog's ways out (`Dialog.setCloseHeld`), Escape and the veil among them, and the update where the send fails lets go of both, the reason on the field (the specimen's rename refuses a name with a slash). `isSubmitDisabled` holds the submit button alone while there is nothing to send (a name unchanged), the fields and cancel live. The fields leave the keyboard as they are held, so the update moves focus to the submit button as it sends (`Focus.Focus({ id: Card.submitId(id) })`), and it still ignores `onSubmit` while busy, for an Enter that raced the render. A ConfirmDialog with `isDanger` still opens on cancel.
|
|
1060
1061
|
|
|
1061
|
-
- **ConfirmDialog** → a Submodel over Dialog (an `alertdialog` when `isDanger`, a `dialog` otherwise) holding a narrow Card whose body is an Outcome (warn, or error when `isDanger`), the Outcome's body describing the dialog. Its view inputs follow the system's words: `title` (the title bar), `question` (the Outcome's title), `body`, and `label` (the confirming button's word). Without `isDanger` the confirm is primary and default; with it the confirm is the red button and cancel is the default, where focus starts. `Confirmed` does not close it: work that takes a while stays in the dialog. `isBusy` holds the confirming button (its word, the working cursor, focus kept, a second press ignored) while cancel stays live; the parent calls `ConfirmDialog.close` when the work lands, or keeps it open while it is not done. `isDisabled` holds it the same way without the working cursor, for an action that is blocked rather than running (writes not accepted yet). `toDetailView` prints the application's own content under the sentence, inside the Outcome's body and so in the dialog's description: what the work said back (a refused delete's reason as an error Notice) or a notice with its own retry. Cancel, Escape and the veil report `Cancelled`; the parent decides whether that also stops work already running, and ignores the work's result once cancelled. Where the reader may not walk away from the work (its failure must land in the dialog),
|
|
1062
|
+
- **ConfirmDialog** → a Submodel over Dialog (an `alertdialog` when `isDanger`, a `dialog` otherwise) holding a narrow Card whose body is an Outcome (warn, or error when `isDanger`), the Outcome's body describing the dialog. Its view inputs follow the system's words: `title` (the title bar), `question` (the Outcome's title), `body`, and `label` (the confirming button's word). Without `isDanger` the confirm is primary and default; with it the confirm is the red button and cancel is the default, where focus starts. `Confirmed` does not close it: work that takes a while stays in the dialog. `isBusy` holds the confirming button (its word, the working cursor, focus kept, a second press ignored) while cancel stays live; the parent calls `ConfirmDialog.close` when the work lands, or keeps it open while it is not done. `isDisabled` holds it the same way without the working cursor, for an action that is blocked rather than running (writes not accepted yet). `toDetailView` prints the application's own content under the sentence, inside the Outcome's body and so in the dialog's description: what the work said back (a refused delete's reason as an error Notice) or a notice with its own retry. Cancel, Escape and the veil report `Cancelled`; the parent decides whether that also stops work already running, and ignores the work's result once cancelled. Where the reader may not walk away from the work (its failure must land in the dialog), the update that starts it holds the ways out (`ConfirmDialog.setCloseHeld`): cancel prints held (`aria-disabled`, in the keyboard, no ring and no shadow while the busy commit keeps focus), and cancel, Escape and a press on the veil change nothing, so the dialog stays open and reports no `Cancelled`; the update where the work fails lets go, and `close` closes it and lets go.
|
|
1062
1063
|
- **CommandPalette** (Submodel over Dialog and `@foldkit/ui`'s Input with a listbox of its own, since a combobox would not let the cursor land on held rows or Escape reach the Dialog) → a card at 18vh holding `div.palette-q` (a `span.palette-scope` and a combobox input), `div.palette-detail` when the app gives `toDetailView`, `div.palette-list[role="listbox"]` of `div.palette-group` headings and `div.palette-row[role="option"][aria-selected]` rows (control register; `isAuthored` titles, `.authored`, set in sans with a glyph and wait for a query), a `div.sr-only[role="status"]`, and `div.palette-more`. Once a query is typed the status says how many rows it finds ("3 results.", "Nothing matches."); it is empty as the card opens and on a fresh page, so it is never mounted holding its words (rule 12). Labels and hints wrap within their row, including long names and destination paths. One cursor: the inverted row, moved by the keys only. ↑ ↓ move it and stop at the ends, each key one row from where the last left it however fast they come (the field's own handler, in the view, reports `PressedArrowKey` with the rows its render showed, and update steps from the Model's cursor); Home and End stay the field's, moving the caret; Enter does what a click on its row does, decided in update from what the render showed each row to be (held, opening a page, a choice on a page): a command closes the palette first so what it opens takes focus, and reports `SelectedCommand`. An arrow or an Enter pressed before the list has caught up with the keys before it (straight after a letter, or before the arrows already waiting have moved the cursor) cannot name a row yet, so it waits for the next render: the row that render shows the cursor on becomes the cursor, and a waiting Enter acts on it as on any row. So arrows typed straight after a letter step through the new query's rows, and an Enter acts on the keys before it, never on letters typed after it. While the palette is open, ↑ ↓ and Enter pressed before its field has focus (straight after the key that opened it) are its own too: `CommandPalette.subscriptions(keyPrefix)` takes them on the window before the page's bindings see them, and they wait as above. A key an IME is composing stays the IME's. The pointer shades the row under it (Header Grey, as a list's row) and a click runs that row; it never moves the cursor and never scrolls. The cursor comes into view on every key that moves it, when the view or the query changes, and when its row leaves the list (it falls back to the first): the app, which changes the list, returns `CommandPalette.ScrollToCursor` with the palette's id; a row that only reflows is not followed. The cursor holds to its row by id while the app updates the list; `CommandPalette.maybeCursorId` reads the row it shows for a list of commands, so the app can compare it before and after its change. A row with `isDisabled` prints muted (`[aria-disabled="true"]`), its `reason` in the hint, and the cursor still lands on it but Enter and a click do nothing. **Pages:** a command with a `page` (a status, a date, a destination) ends in a caret and opens that page in place instead of closing: `div.palette-q` holds a Back IconButton, the scope chip (what the commands act on, kept on every page), the page's title (`span.palette-title`, pixel caps), then the page's own field (placeholder `find`, else "type to narrow"), focused. A long scope wraps inside its chip and the field takes its own line when too little is left beside it, so nothing leaves the card at 390px. The choices are found in like the commands. The choice in force now (`current`) carries a `current` tag and cannot be picked again. The cursor starts on the page's `start` (the next status; the first choice a typed query finds), else on `current`, else on the first choice, read while the reader has not moved it, so an application that keeps `start` steady across a change made elsewhere moves the tag, not the cursor. Back, or Backspace in an empty field, returns to the commands with their query and cursor as they were, also when the palette opened straight on a page (`CommandPalette.openAt`). A pick (`SelectedChoice`) holds the page until the parent answers: the row prints the working cursor, the list is `aria-busy`, and moving, picking, typing and Back wait. With `CommandPalette.pickSucceeded` the palette closes; with `pickFailed` the page stays with its query, focus and the cursor on that choice, and the app says why in `toDetailView`, which shows on the commands and on every page alike. Escape closes the whole palette, even while a pick is out, and reports `Closed`; an answer arriving after that, or after the page was left, changes nothing. A page whose command leaves the list gives way to the commands. One level: choices do not open pages.
|
|
1063
1064
|
- **Keymap** → `div.keymap > div > h3 + div.keymap-row > kbd + span`: keys in the 11px pixel face under 10px muted group heads, for a narrow Card.
|
|
1064
1065
|
|
|
@@ -1094,6 +1095,10 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
|
|
|
1094
1095
|
|
|
1095
1096
|
**Board** (Submodel over `@foldkit/ui`'s DragAndDrop. `Board.setMovable` says whether cards may move, and turning it off cancels a move. It reports `Moved`, and `SelectedCard` for a press on a card's body, never on a control inside it or anything marked `[data-no-lift]`. While a card is held the application pauses its own keys. `Board.subscriptions(keyPrefix)` are lifted into the application's once per board, keyed behind the parent's prefix: `Board.subscriptions("queueBoard")` is keyed `queueBoardPointer`, `queueBoardKeyboard`, `queueBoardAutoScroll` and `queueBoardEscape`, so two boards on one page take two prefixes) → `div.board` (`--cols`) of `div.board-col (.drop-target)`, each a panel holding `ul.board-cards`; each card is the parent's `toCardView`, which receives what the board hands it (`render`) and returns **Board.cardView** → `article.board-card (.movable, .drag-dim, .lifted)`: a paper slip in a 2px ink border with a 3px shadow, `padding: 9px 11px`, a 7px gap between a 2-line-clamped sans `button.title` (`isDisabled` holds it while work runs: `[aria-disabled="true"]`, it ignores presses but keeps its words and its place in the keyboard), or, while the title is edited, the caller's `editor` in its place (an `InlineInput` and its `errorView`, ink on paper even on the cursor's inverted slip; the lifted copy keeps the title), and `div.meta-row` (mark, tag, chip, `span.gap`, age). While cards may move (`isMovable`, the default; `Board.init` takes `isMovable: false` for a board whose moves wait on something, such as writes not accepted yet) they move between columns and the meta row ends in a quiet `button.iconbtn.grip` (the grip glyph, labelled "Move <label>", `aria-roledescription="draggable"`, `aria-pressed` from `Button.pressedAttribute`, present in both states: `"true"` while held, `"false"` otherwise). A mouse drags the whole slip (`cursor: grab`); touch and keyboard take the grip (`touch-action: none`, so a finger on the slip still scrolls): Space or Enter picks up, the arrow keys jump one column in reading order, Space drops at the top of that column, Escape cancels, and each step is announced through the page's live region in words from the card's `label` and the column titles. The held card dims to 40% in place while a lifted copy follows the pointer on the 8px card shadow, tinted head (`aria-hidden`, `inert`, taking none of the card's attributes); the receiving column outlines its panel with a 2px dashed ink line offset 4px and tints its list head; a drop lands at once, with no animation. The cards and columns are the parent's: `Moved` counts `toIndex` among the destination's other cards and names the cards the moved one now sits between (for a list ordered by neighbours); a drop where the card already was, or while cards may not move, reports nothing; and the parent moves the item, or decides not to. Hover and focus-within tint the card; focus-within rings the whole slip 2px outside. `[aria-current="true"]`, set on the article by the application through `attributes`, is the board's cursor, printed inverted (ink fill, paper text) and keeping its ink on hover and focus-within. Inside it, what prints muted on paper (`.glyph`, `.age`, `.due`, `.tag`, `.done-title`, a quiet icon button) prints in paper at 0.75, as a title bar's meta does, and a tag's border takes `currentColor`; chips, marks and buttons keep their own fills; a late due day or stale age, which cannot be red on ink, becomes a red slip with `on-red` text, its box drawn by a 3px red spread so nothing moves; a ring inside prints in paper; the application moves it (see Keys). The lifted copy prints head-tinted, never inverted.
|
|
1096
1097
|
|
|
1098
|
+
### Sortable
|
|
1099
|
+
|
|
1100
|
+
**Sortable** (Submodel over `@foldkit/ui`'s DragAndDrop, for items the reader puts in order: a list of rows or a grid of tiles) → `div.sortable (.grid)` holding `ul.sortable-items[aria-label]` (a plain list: no listbox, so an item's content may hold anything) of `li.sortable-item (.movable, .drag-dim, .drop-before, .drop-after)`, each a grip, then `div.sortable-body` with the parent's content, and the list's keyboard help in a `p.sr-only`. `"List"` (the default) prints rows edge to edge between 1px rules, for a flush panel, `8px 16px 8px 10px`, tinted head on hover; `"Grid"` prints tiles that wrap, 180px and up, 12px apart: paper slips in a 2px ink border on the 3px print shadow. Each item starts with a quiet `button.iconbtn` grip (the grip glyph, named "Move <label>", `aria-roledescription="draggable"`, `aria-pressed`, described by the keyboard help); while moves are held it stays, held (`aria-disabled`, in the keyboard), so focus on it stays put when holding moves cancels one. A mouse drags an item by its body or its grip (`cursor: grab`); touch takes the grip alone (`touch-action: none` on a movable grip only, so a finger on a row scrolls the page); the keyboard takes the grip too: Space or Enter picks the item up, every arrow moves it one place in reading order, Space or Enter drops it, Escape puts it back (taken before a dialog or the page's keys see it), and Tab leaves it where it is. A press on a control inside an item, or on anything `[data-no-lift]`, never lifts it. Only the pointer that pressed moves the item; the browser cancelling it puts the item back. The held item dims to 40% in place (`.drag-dim`) and keeps its own print; a copy of it, `.lifted`, tinted head on the 8px card shadow in a 2px ink border (`aria-hidden`, `inert`, rendered with `isLifted`, so it carries no ids), follows the pointer in `div.sortable-lift`, or, held by the keyboard, prints in the list where it would land, in `li.sortable-slot`, counted as no item. Where a pointer would drop it, a 2px dashed ink line prints beside the item it lands next to (`.drop-before`, or `.drop-after` past the last), over the layout, so nothing moves under the pointer. The pointer's place is read from the item whose centre is closest, across where items sit side by side and down a column, and read again where the pointer is let go, so a page that scrolled under a still pointer drops where the pointer is. Each step is said through the page's live region in words from the item's `label` and its place, counted from one ("Beta, 3 of 4."). The items are the parent's: a drop reports `Moved`, and the parent moves the item, or decides not to, and flashes it; a drop where the item was, outside the list, or while moves are held reports nothing. While a pointer presses or carries an item the page's root carries `data-pixel-paper-dragging`, which prints the grabbing cursor everywhere and holds text selection.
|
|
1101
|
+
|
|
1097
1102
|
### Inputs / Fields
|
|
1098
1103
|
|
|
1099
1104
|
- **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.
|
|
@@ -1104,7 +1109,7 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
|
|
|
1104
1109
|
- **Textarea** (TextareaField's): boxless, 13px/1.5 sans, grows with content from 96px, vertical resize with an ink corner grip. **Mono:** system mono at 12px.
|
|
1105
1110
|
- **Select** (SelectField's): `appearance: base-select` where supported; 30px minimum, 2px ink border, 11px pixel face, a CSS-drawn caret; the picker is paper with a 2px border and the panel shadow. Invalid, its box turns red.
|
|
1106
1111
|
- **Choice** (a View over `@foldkit/ui`'s Checkbox) → `div.choice > button.choice-box[role="checkbox"][aria-checked] + span > span.choice-name + span.choice-note`; **RadioChoices** (Submodel over `@foldkit/ui`'s RadioGroup) → `div.choices[role="radiogroup"] > button.choice[role="radio"][aria-checked] > span.choice-box + span > span.choice-name + span.choice-note`: the option is the button itself, so a click anywhere on its line chooses it and the focus ring is around the line; `RadioChoices.firstOptionId` is the id of the first option, for a link to the group (an ErrorSummary's); **Choices** → `div.choices (.inline)`. 14px ink-bordered squares with a 6px painted cell (radio: 8px diamond). Invalid (`aria-invalid` on the radiogroup, since a radio does not take it), each box and choice's name turns red.
|
|
1107
|
-
- **ErrorSummary** → `div.notice.error.error-summary[tabindex="-1"]`: why a form was not sent, inside the form above its fields: the error Notice with a count headline ("2 fields need attention") and a list of links, ink on a 1px underline (2px on hover), each a fragment link to its failed field, one entry per control (the headline counts entries and each `li` is keyed by its control's id, so several messages for one control are the caller's to join), which the application's
|
|
1112
|
+
- **ErrorSummary** → `div.notice.error.error-summary[tabindex="-1"]`: why a form was not sent, inside the form above its fields: the error Notice with a count headline ("2 fields need attention") and a list of links, ink on a 1px underline (2px on hover), each a fragment link to its failed field, one entry per control (the headline counts entries and each `li` is keyed by its control's id, so several messages for one control are the caller's to join), which the application's URL handler focuses (`SkipLink.ScrollToFragment`). It is rendered from the errors found at the failed submit, not live state, and holds still while they are put right; each field it names is marked invalid too. With no errors it renders nothing. It carries no `role="alert"` (a live region inserted with its words in it is not reliably read): the form's update returns `Focus` with the summary's id at every failed submit, a transition the submit causes, so focus lands on it once it has rendered and reads it, again at each failed submit though it was already showing. A refusal that arrives after the submit (a server turning a field down while the save is busy) is a failed submit like any other: the form renders the summary from it and focuses it.
|
|
1108
1113
|
- **DropZone** (Submodel over `@foldkit/ui`'s FileDrop) → `div.drop-zone ([data-drag-over])`: a 2px dashed ink box, `28px 16px`, centred, a 34px ink-bordered glyph box, a 12px uppercase headline and a 44ch muted sentence, and a `label.btn.small` around a visually hidden file input; solid and head-tinted while a file hovers (FileDrop sets `data-drag-over`).
|
|
1109
1114
|
|
|
1110
1115
|
### Notices
|
|
@@ -1134,7 +1139,7 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
|
|
|
1134
1139
|
|
|
1135
1140
|
### Meters and cells
|
|
1136
1141
|
|
|
1137
|
-
**Cells** (`Meter.cellsView`) → `span.cells[role="meter"] > span.cell (.on)` (`.short` for 24×8px cells). **Meter** (over `@foldkit/ui`'s Meter) → `li.meter-row (.stale)` in `Meter.listView` → `ul.meters`: a reading against a maximum in the vocabulary of `<meter>` (`value`, `max`): the label (name register) and an optional detail (meta) beside twenty cells lit in ink up to the reading; then the reading's state (the percentage in the 12px pixel face below 80% of max, the amber reading mark from 80%, a red chip at max holding `full` or the percentage; below max it never prints 100% or lights every cell, so 49,999 of 50,000 reads 99% with 19 cells); then the numbers (or `valueText`) in 11px muted tabular figures, formatted in English (`41,200`) so server and browser agree; then `div.meter-foot` (resets, last read; `.late` red). A stale meter (`isStale`: the last reading failed, the numbers are the last known) prints its lit cells muted and its name ends in "(stale)" for screen readers (`span.sr-only`), so the state never rests on colour; the caller says why and when it was last read in the foot line.
|
|
1142
|
+
**Cells** (`Meter.cellsView`) → `span.cells[role="meter"] > span.cell (.on)` (`.short` for 24×8px cells). **Meter** (over `@foldkit/ui`'s Meter) → `li.meter-row (.stale)` in `Meter.listView` → `ul.meters`: a reading against a maximum in the vocabulary of `<meter>` (`value`, `max`): the label (name register) and an optional detail (meta) beside twenty cells lit in ink up to the reading; then the reading's state (the percentage in the 12px pixel face below 80% of max, the amber reading mark from 80%, a red chip at max holding `full` or the percentage; below max it never prints 100% or lights every cell, so 49,999 of 50,000 reads 99% with 19 cells); then the numbers (or `valueText`) in 11px muted tabular figures, formatted in English (`41,200`) so server and browser agree, left out with `hasNumbers: false` where they would repeat the percentage (`valueText: "62% used"` beside 62%; screen readers still hear it); then `div.meter-foot` (resets, last read; `.late` red). `ul.meters` is a size container (`container: meters`), so a row lays out by the column it is placed in, not the window: from 560px the name and detail sit in a left column (180px to 240px) beside the cells; narrower, the row stacks and the numbers take a line of their own; under 280px, 240px and 200px the cells step down to 8, 6 and 4px wide, whole pixels. A meter list fits any column from 160px; give it the column's width (a flush panel's body), since a size container takes no width from its content. A stale meter (`isStale`: the last reading failed, the numbers are the last known) prints its lit cells muted and its name ends in "(stale)" for screen readers (`span.sr-only`), so the state never rests on colour; the caller says why and when it was last read in the foot line.
|
|
1138
1143
|
|
|
1139
1144
|
### Stats
|
|
1140
1145
|
|
|
@@ -1165,7 +1170,7 @@ All three keep their space from the first paint (`size`: `Media.FrameSize.Ratio(
|
|
|
1165
1170
|
|
|
1166
1171
|
### Signature: the invert flash and the flash word
|
|
1167
1172
|
|
|
1168
|
-
Every committed change (an approval, a drop onto a column, a save, a rename, a rotate, a copy) flashes the changed element: `Announcer.
|
|
1173
|
+
Every committed change (an approval, a drop onto a column, a save, a rename, a rotate, a copy) flashes the changed element: the application's update returns `Announcer.Flash({ selector })`, beside `Announcer.Announce`. It runs as a Web Animation, not a class: `filter: invert(1)` held for the middle half of `--motion-flash` (0.7s), so the element goes fully inverted and back with no tween, and a second commit mid-flash restarts it. Under `prefers-reduced-motion: reduce` the flash becomes a 2px ink ring inset 2px, held for the beat and then gone, and every cursor stops. `Announcer.Announce({ text })` says what changed through the page's one polite live region, `Announcer.view` (`div.sr-only[role="status"][aria-live="polite"]`, empty in the pre-rendered HTML); words said in the same moment are read together, and the same words twice are read twice. **Hosting rule:** render exactly one live region, and put it where it can be heard. While a Dialog is open the page outside it is inert and hidden from screen readers, so the region renders inside the topmost open dialog (its `toLiveRegionView` view input; ConfirmDialog, KeyHelp and CommandPalette pass theirs through), and in the body otherwise. `LiveRegion.place` decides it from the application's Model on every render: it takes every dialog that can host, from the bottom of the stack to the top (one that opens from another comes after it), each with whether it is open, and returns the `toLiveRegionView` for each dialog (the parts for the topmost open one, `undefined` for the rest) and the parts for the end of the body (only while none is open), so the parts render exactly once and move as dialogs open and close. `LiveRegion.view` puts the parts together: the Announcer's region, the toast stack and a Banner in force, which follow the same rule. The live region is the element marked `data-pixel-paper-announcer` (`Announcer.ANNOUNCER_ATTRIBUTE`); a Loading line is a `role="status"` too, so a test that counts the page's live regions counts that marker, not the role. A FlashWord beside the action says the verb (see Notices).
|
|
1169
1174
|
|
|
1170
1175
|
### Glyph inventory
|
|
1171
1176
|
|
|
@@ -1180,14 +1185,16 @@ Installing: see README.md beside this file. The package is a Foldkit library. It
|
|
|
1180
1185
|
- Import the `./pixel-paper.css` export from the client entry, and write `<script>${Scheme.schemeScript}</script>` into the document head.
|
|
1181
1186
|
- Provide `Clipboard.layer` from `@effect/platform-browser` as the application's `resources` where a CopyButton is used. The scheme needs no service: SchemeSwitch reads and writes the page itself.
|
|
1182
1187
|
- Keep the key platform in the application's Model, as `KeyHint.KeyPlatform` (start with `"Other"`, so the server and the browser render the same), and set it once from `init` with one Command that runs `KeyHint.detectKeyPlatform`. Every KeyHint, Button `keyHint` and KeyHelp reads it from there.
|
|
1183
|
-
-
|
|
1184
|
-
- Route fragment links (a SkipLink, an ErrorSummary's links) through the application's URL handler, which
|
|
1188
|
+
- Place the page's live parts (the Announcer, the toast stack, a Banner in force) with `LiveRegion.place` and `LiveRegion.view`, by the hosting rule above; render the Tips layer once, and one KeyHelp per page.
|
|
1189
|
+
- Route fragment links (a SkipLink, an ErrorSummary's links) through the application's URL handler, which returns `SkipLink.ScrollToFragment` for every changed URL: it focuses a `#fragment` and scrolls a new page to its top. Put `CompletedScrollToFragment: {}`, `CompletedAnnounce: {}` and `CompletedFlash: {}` in the application's Message union for the shipped Commands it runs; its update changes nothing for them.
|
|
1190
|
+
|
|
1191
|
+
**Content-Security-Policy.** The stylesheet, the tokens and the fonts load from the package, so `'self'` covers them, and the textures (the dither, the veil's grain) are gradients, not images: `img-src` needs nothing for them. Two things a strict policy must allow: the inline `schemeScript` in the head (its hash or a nonce in `script-src`), and, on a page rendered on the server, the `style` attributes a few views write into the markup (a board's column count, a chart's cells, a picture's or a video's ratio), which need `style-src-attr 'unsafe-inline'`. A pointer drag on a Board adds a `<style>` element for its while (`@foldkit/ui`'s DragAndDrop: the grabbing cursor and no text selection); without `style-src 'unsafe-inline'` the drag still moves, with the page's own cursor and selection. A Sortable marks the root instead and needs nothing. A texture token is a `background` shorthand: `background: var(--dither)` paints the ground with its own base, and `background: var(--veil-grain), var(--veil)` a veil.
|
|
1185
1192
|
|
|
1186
1193
|
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.
|
|
1187
1194
|
|
|
1188
|
-
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-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 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.
|
|
1195
|
+
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 is pressed or carried, `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 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.
|
|
1189
1196
|
|
|
1190
|
-
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
|
|
1197
|
+
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.
|
|
1191
1198
|
|
|
1192
1199
|
## Building components
|
|
1193
1200
|
|
|
@@ -1204,15 +1211,15 @@ Each component is `src/<name>/<name>.ts` with an explicit `index.ts`, a colocate
|
|
|
1204
1211
|
5. **One vocabulary.** `label` is a control's words (a button's, a field's, a group's name, a dialog's name when no heading says it). `title` is a heading (a card's bar, a notice's or an outcome's caps line). `body` is content. A ConfirmDialog's `title` is its bar, `question` its Outcome's title, `label` its confirming button. Booleans read as questions (`isBusy`, `isOpen`, `hasRetry`) and literal choices are capitalised (`"Primary"`, `"Warn"`). What a Model, a Message or a Command's args may lack is an `Option` field named `maybeX` (`maybeError`, `maybeLastSavedId`), and an accessor that returns one keeps the prefix (`InlineInput.maybeError`, `CommandPalette.maybeCursorId`); optional view config is a plain `?:` field under its own name (`help?`, `full?`).
|
|
1205
1212
|
6. **Slots.** A slot is a plain value unless it may carry the application's handlers; then it is a `to<Slot>View` function. In a View's config a content slot (`body`, `detail`, `actions`, `lead`) is a value, `ReadonlyArray<Html | string>`. In a Submodel's view inputs, a markup slot that may carry handlers is a top-level function, `toXView: () => Html` (`toPanelView`, `toAsideView`, `toDetailView`, `toLiveRegionView`, Dialog's `toView`, which receives its attribute groups), never nested Html carrying the parent's handlers, because a Submodel's view runs in its own Message boundary (see Foldkit's `html` and `submodel` modules).
|
|
1206
1213
|
7. **Parents never read a child's Model.** A Submodel exports an accessor for what a parent reads (`Dialog.id`, `Dialog.isOpen`, `ConfirmDialog.isOpen`, `KeyHelp.id`, `SchemeSwitch.scheme`), and the parent calls it, including for `slotId`. An accessor nothing outside its own file and test uses is not exported. No field is kept twice.
|
|
1207
|
-
8. **Instances are scoped by id.** The DOM ids and hooks of a reusable Submodel are scoped by its instance id (a board's columns are `${boardId}/${columnId}`), so two instances can share a page. Its Subscription keys are scoped too: `Subscription.lift` keeps a child's keys and `Subscription.aggregate` throws on a key two records share, so a Submodel whose Subscriptions could run twice on one page exports them as a factory taking a key prefix from the parent, `subscriptions(keyPrefix)` (Board, Tips, CommandPalette), which puts every key behind it (`Board.subscriptions("queueBoard")` is keyed `queueBoardPointer`…). The parent never renames a child's keys, and gates a whole lifted record with `when` rather than listing its keys. A Submodel that is one per page by nature (SchemeSwitch, KeyHelp) keeps fixed keys named for it (`schemeSwitchSystemPalette`, `helpKey`).
|
|
1214
|
+
8. **Instances are scoped by id.** The DOM ids and hooks of a reusable Submodel are scoped by its instance id (a board's columns are `${boardId}/${columnId}`), so two instances can share a page. Its Subscription keys are scoped too: `Subscription.lift` keeps a child's keys and `Subscription.aggregate` throws on a key two records share, so a Submodel whose Subscriptions could run twice on one page exports them as a factory taking a key prefix from the parent, `subscriptions(keyPrefix)` (Board, Sortable, Tips, CommandPalette), which puts every key behind it (`Board.subscriptions("queueBoard")` is keyed `queueBoardPointer`…). The parent never renames a child's keys, and gates a whole lifted record with `when` rather than listing its keys. A Submodel that is one per page by nature (SchemeSwitch, KeyHelp) keeps fixed keys named for it (`schemeSwitchSystemPalette`, `helpKey`).
|
|
1208
1215
|
9. **A Scene test for every accessibility contract** a component adds beyond `@foldkit/ui`: roles, accessible names, `aria-pressed`, `aria-describedby`, `aria-invalid`, held and disabled states, alerts. Behaviour that only a real event shows (a Mount's key handling) gets a test with real DOM events in happy-dom, no mocks.
|
|
1209
1216
|
10. **Toggles.** `aria-pressed` comes only from `Button.pressedAttribute(isPressed, h)`, present in both states so the control reads as a toggle.
|
|
1210
1217
|
11. **Naming code.** Never put `aria-label` on `code` or `pre` alone; give them `role="group"` and a name.
|
|
1211
|
-
12. **Live regions follow the topmost open dialog.** An open Dialog makes the page outside it inert and hidden from screen readers. Anything that must be heard or reached while it is open (the Announcer's live region, the toast stack) renders inside the topmost open dialog, through its `toLiveRegionView: () => Html` (rendered inside the `<dialog>`, outside the card), and in the body otherwise. Every dialog-like Submodel (ConfirmDialog, KeyHelp, CommandPalette) passes `toLiveRegionView` through. A Menu does not trap focus, so it hosts nothing.
|
|
1218
|
+
12. **Live regions follow the topmost open dialog.** An open Dialog makes the page outside it inert and hidden from screen readers. Anything that must be heard or reached while it is open (the Announcer's live region, the toast stack) renders inside the topmost open dialog, through its `toLiveRegionView: () => Html` (rendered inside the `<dialog>`, outside the card), and in the body otherwise. Every dialog-like Submodel (ConfirmDialog, KeyHelp, CommandPalette) passes `toLiveRegionView` through. A Menu does not trap focus, so it hosts nothing. `LiveRegion.place` decides which host from the application's Model, and the application lists its dialogs in stacking order (the specimen's `site/src/liveRegion.ts`, shown under Setting up). A `role="status"` element stays in the DOM with only its contents swapped; it is never mounted already holding its message, which would go unheard.
|
|
1212
1219
|
13. **The key platform lives in the application's Model** (`KeyHint.KeyPlatform`, starting `"Other"` so server and browser render alike), set once by one init Command that runs `KeyHint.detectKeyPlatform`. Components take `platform` as an input; none detects it.
|
|
1213
|
-
14. **
|
|
1214
|
-
15. **A Mount reports through the parent.** A View that owns a Mount takes a required `toParentMessage: (message: X.Message) => ParentMessage` (TopBar, StatusBar, Toolbar, the mermaid adapter), and a fire-and-forget Mount maps to a caller `Completed*` Message the parent may ignore. A bare `onX: Message` is used only where the parent's Model acts on the fact (ImageFrame's `onBroken`). In a Scene, resolve the Mount by name with the lifted Message: `Scene.Mount.resolve({ name: X.Mount.name }, toParentMessage(...))`.
|
|
1215
|
-
16. **Fragment links focus through the application.** A link to `#id` (SkipLink, an ErrorSummary's links) needs the application's URL handler to
|
|
1220
|
+
14. **Shipped Commands for page-wide side effects.** What every application would otherwise write for itself is a Command in its namespace (`Focus.Focus`, `Announcer.Announce`, `Announcer.Flash`, `SkipLink.ScrollToFragment`), reporting a `Completed*` Message from that namespace's `Message` union, which the application's own union declares too (`CompletedAnnounce: {}`), so the tag matches and its update changes nothing for it. One Command per effect, so a Story test resolves it by its one name. A component that says or focuses something as part of its own work keeps a Command named for that work (`Board.AnnounceMove`, `CopyButton.AnnounceCopy`), so its name never collides with the application's.
|
|
1221
|
+
15. **A Mount reports through the parent.** A View that owns a Mount takes a required `toParentMessage: (message: X.Message) => ParentMessage` (TopBar, StatusBar, Toolbar, the mermaid adapter), and a fire-and-forget Mount maps to a caller `Completed*` Message the parent may ignore. A bare `onX: Message` is used only where the parent's Model acts on the fact (ImageFrame's `onBroken`). In a Scene, resolve the Mount by name with the lifted Message: `Scene.Mount.resolve({ name: X.Mount.name }, toParentMessage(...))`. Where every application's Scene must resolve a component's Mounts to render a page at all, the namespace exports `sceneMounts`, the matchers paired with Messages that change nothing, for `Scene.Mount.resolveAll(...X.sceneMounts)`: `TopBar.sceneMounts(toParentMessage)`, `Toast.sceneMounts` and `Dialog.sceneMounts` (which serves ConfirmDialog, KeyHelp and CommandPalette too).
|
|
1222
|
+
16. **Fragment links focus through the application.** A link to `#id` (SkipLink, an ErrorSummary's links) needs the application's URL handler to return `SkipLink.ScrollToFragment` for the changed URL, since Foldkit routes the click.
|
|
1216
1223
|
17. **Platform services are the application's.** A component that needs a platform service says so in its TSDoc and here (CopyButton writes through Effect's `Clipboard`), and the application provides the Layer in its `resources`.
|
|
1217
1224
|
18. **Upstream gaps.** Where `@foldkit/ui` lacks a capability, the workaround carries `// NOTE: @foldkit/ui gap: <what>` at its site, and that note is the record: there is no list beside it. A wrapper that must build a child Message itself says so in the same note, with a lint disable on that line alone.
|
|
1218
1225
|
|
|
@@ -1,42 +1,50 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import * as Command from "foldkit/command";
|
|
2
3
|
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
3
|
-
/**
|
|
4
|
+
/** What `Announce` and `Flash` report. An application that runs them puts
|
|
5
|
+
* `CompletedAnnounce: {}` and `CompletedFlash: {}` in its own Message union, which these tags
|
|
6
|
+
* match, and its update changes nothing for them. */
|
|
7
|
+
export declare const Message: import("foldkit/message").MessageUnion<{
|
|
8
|
+
readonly CompletedAnnounce: {};
|
|
9
|
+
readonly CompletedFlash: {};
|
|
10
|
+
}>;
|
|
11
|
+
export type Message = typeof Message.Type;
|
|
12
|
+
/** The attribute that marks the page's live region, which a test finds it by: Loading's line
|
|
13
|
+
* is a `role="status"` too, so counting that role counts more than the one region. */
|
|
4
14
|
export declare const ANNOUNCER_ATTRIBUTE = "data-pixel-paper-announcer";
|
|
5
15
|
/**
|
|
6
16
|
* The page's one polite live region, empty until something is said. Render exactly one,
|
|
7
17
|
* placed by the rule every pixel-paper application follows, because an open Dialog makes
|
|
8
|
-
* everything outside it inert and hidden from screen readers:
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `toLiveRegionView` (ConfirmDialog, KeyHelp and CommandPalette pass theirs through);
|
|
12
|
-
* - otherwise at the end of the body.
|
|
13
|
-
*
|
|
14
|
-
* Decide which from the application's Model (which dialog is open), so the region moves
|
|
15
|
-
* with the dialogs. A Toast stack follows the same rule.
|
|
18
|
+
* everything outside it inert and hidden from screen readers: inside the topmost open Dialog,
|
|
19
|
+
* otherwise at the end of the body. `LiveRegion.view` renders it with the toast stack, and
|
|
20
|
+
* `LiveRegion.place` puts them where the rule says.
|
|
16
21
|
*/
|
|
17
22
|
export declare const view: <Message>(h: HtmlBuilder<Message>) => Html;
|
|
23
|
+
/** Says `text` to screen readers through the page's live region, once it has rendered. A
|
|
24
|
+
* component that says its own steps (a board's move, a copy) runs it from a Command of its
|
|
25
|
+
* own, named for what it says. */
|
|
26
|
+
export declare const announce: (text: string) => Effect.Effect<void>;
|
|
27
|
+
/** Inverts the element `selector` finds for one beat; `Flash` runs it. */
|
|
28
|
+
export declare const flash: (selector: string) => Effect.Effect<void>;
|
|
18
29
|
/**
|
|
19
30
|
* Says `text` to screen readers through the page's live region (see `view`). Words said in
|
|
20
31
|
* the same moment are read together, and the region clears first, so the same words twice
|
|
21
|
-
* are read twice. Without a region on the page it does nothing.
|
|
22
|
-
*
|
|
23
|
-
* Run it from the application's own Command, as Foldkit's `Dom` Effects are run, so its
|
|
24
|
-
* result is the application's Message:
|
|
25
|
-
*
|
|
26
|
-
* ```ts
|
|
27
|
-
* const Announce = Command.define("Announce", {
|
|
28
|
-
* args: { text: Schema.String },
|
|
29
|
-
* messages: [Message.CompletedAnnounce],
|
|
30
|
-
* execute: ({ text }) => Announcer.announce(text).pipe(Effect.as(Message.CompletedAnnounce())),
|
|
31
|
-
* });
|
|
32
|
-
* ```
|
|
32
|
+
* are read twice. Without a region on the page it does nothing. Reports `CompletedAnnounce`.
|
|
33
33
|
*/
|
|
34
|
-
export declare const
|
|
34
|
+
export declare const Announce: Command.CommandDefinitionWithArgs<"Announce", {
|
|
35
|
+
text: Schema.String;
|
|
36
|
+
}, Effect.Effect<{
|
|
37
|
+
readonly _tag: "CompletedAnnounce";
|
|
38
|
+
}, never, never>>;
|
|
35
39
|
/**
|
|
36
40
|
* Inverts the element `selector` finds for one beat (`--motion-flash`): the signal that a
|
|
37
41
|
* change was committed. A second flash mid-beat restarts it. With `prefers-reduced-motion`
|
|
38
|
-
* it holds a still 2px ink ring for the beat instead.
|
|
39
|
-
* what the flash shows
|
|
42
|
+
* it holds a still 2px ink ring for the beat instead. Return it beside `Announce`, which says
|
|
43
|
+
* what the flash shows. Reports `CompletedFlash`.
|
|
40
44
|
*/
|
|
41
|
-
export declare const
|
|
45
|
+
export declare const Flash: Command.CommandDefinitionWithArgs<"Flash", {
|
|
46
|
+
selector: Schema.String;
|
|
47
|
+
}, Effect.Effect<{
|
|
48
|
+
readonly _tag: "CompletedFlash";
|
|
49
|
+
}, never, never>>;
|
|
42
50
|
//# sourceMappingURL=announcer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"announcer.d.ts","sourceRoot":"","sources":["../../../src/announcer/announcer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,MAAM,
|
|
1
|
+
{"version":3,"file":"announcer.d.ts","sourceRoot":"","sources":["../../../src/announcer/announcer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,MAAM,EAAU,MAAM,EAAQ,MAAM,QAAQ,CAAC;AACvE,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMtD;;sDAEsD;AACtD,eAAO,MAAM,OAAO;;;EAGlB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAC;AAI1C;uFACuF;AACvF,eAAO,MAAM,mBAAmB,+BAA+B,CAAC;AAIhE;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GAAI,OAAO,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAMpD,CAAC;AA6CL;;mCAEmC;AACnC,eAAO,MAAM,QAAQ,SAAU,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAQtD,CAAC;AA6CL,0EAA0E;AAC1E,eAAO,MAAM,KAAK,aAAc,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAMvD,CAAC;AAIL;;;;GAIG;AACH,eAAO,MAAM,QAAQ;;;;iBAInB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,KAAK;;;;iBAIhB,CAAC"}
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
|
|
2
|
-
import { Array, Duration, Effect, Option, pipe } from "effect";
|
|
2
|
+
import { Array, Duration, Effect, Option, Schema, pipe } from "effect";
|
|
3
|
+
import * as Command from "foldkit/command";
|
|
4
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
5
|
import * as Render from "foldkit/render";
|
|
6
|
+
// MESSAGE
|
|
7
|
+
/** What `Announce` and `Flash` report. An application that runs them puts
|
|
8
|
+
* `CompletedAnnounce: {}` and `CompletedFlash: {}` in its own Message union, which these tags
|
|
9
|
+
* match, and its update changes nothing for them. */
|
|
10
|
+
export const Message = defineMessageUnion({
|
|
11
|
+
CompletedAnnounce: {},
|
|
12
|
+
CompletedFlash: {},
|
|
13
|
+
});
|
|
4
14
|
// HOSTING
|
|
5
|
-
/** The attribute that marks the page's live region
|
|
15
|
+
/** The attribute that marks the page's live region, which a test finds it by: Loading's line
|
|
16
|
+
* is a `role="status"` too, so counting that role counts more than the one region. */
|
|
6
17
|
export const ANNOUNCER_ATTRIBUTE = "data-pixel-paper-announcer";
|
|
7
18
|
const ANNOUNCER_SELECTOR = `[${ANNOUNCER_ATTRIBUTE}]`;
|
|
8
19
|
/**
|
|
9
20
|
* The page's one polite live region, empty until something is said. Render exactly one,
|
|
10
21
|
* placed by the rule every pixel-paper application follows, because an open Dialog makes
|
|
11
|
-
* everything outside it inert and hidden from screen readers:
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* `toLiveRegionView` (ConfirmDialog, KeyHelp and CommandPalette pass theirs through);
|
|
15
|
-
* - otherwise at the end of the body.
|
|
16
|
-
*
|
|
17
|
-
* Decide which from the application's Model (which dialog is open), so the region moves
|
|
18
|
-
* with the dialogs. A Toast stack follows the same rule.
|
|
22
|
+
* everything outside it inert and hidden from screen readers: inside the topmost open Dialog,
|
|
23
|
+
* otherwise at the end of the body. `LiveRegion.view` renders it with the toast stack, and
|
|
24
|
+
* `LiveRegion.place` puts them where the rule says.
|
|
19
25
|
*/
|
|
20
26
|
export const view = (h) => __foldkitBrandViewResult((h.div([
|
|
21
27
|
h.Class("sr-only"),
|
|
@@ -51,22 +57,9 @@ const sayIn = (region, text) => __foldkitBrandViewResult((Effect.gen(function* (
|
|
|
51
57
|
yield* Effect.sleep(GATHER);
|
|
52
58
|
yield* Effect.sync(() => __foldkitBrandViewResult((speak(region)), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#anonymous~6"));
|
|
53
59
|
})), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#sayIn");
|
|
54
|
-
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* are read twice. Without a region on the page it does nothing.
|
|
58
|
-
*
|
|
59
|
-
* Run it from the application's own Command, as Foldkit's `Dom` Effects are run, so its
|
|
60
|
-
* result is the application's Message:
|
|
61
|
-
*
|
|
62
|
-
* ```ts
|
|
63
|
-
* const Announce = Command.define("Announce", {
|
|
64
|
-
* args: { text: Schema.String },
|
|
65
|
-
* messages: [Message.CompletedAnnounce],
|
|
66
|
-
* execute: ({ text }) => Announcer.announce(text).pipe(Effect.as(Message.CompletedAnnounce())),
|
|
67
|
-
* });
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
60
|
+
/** Says `text` to screen readers through the page's live region, once it has rendered. A
|
|
61
|
+
* component that says its own steps (a board's move, a copy) runs it from a Command of its
|
|
62
|
+
* own, named for what it says. */
|
|
70
63
|
export const announce = (text) => __foldkitBrandViewResult((Effect.gen(function* () {
|
|
71
64
|
yield* Render.afterCommit;
|
|
72
65
|
const maybeRegion = yield* Effect.sync(topmostRegion);
|
|
@@ -95,15 +88,32 @@ const runFlash = (element) => {
|
|
|
95
88
|
element.animate({ filter: ["invert(1)", "invert(1)"] }, { duration: beat / 2, delay: beat / 4, id: FLASH_ID });
|
|
96
89
|
}
|
|
97
90
|
};
|
|
98
|
-
/**
|
|
99
|
-
* Inverts the element `selector` finds for one beat (`--motion-flash`): the signal that a
|
|
100
|
-
* change was committed. A second flash mid-beat restarts it. With `prefers-reduced-motion`
|
|
101
|
-
* it holds a still 2px ink ring for the beat instead. Pair it with `announce`, which says
|
|
102
|
-
* what the flash shows, and run it from the application's own Command as `announce` is.
|
|
103
|
-
*/
|
|
91
|
+
/** Inverts the element `selector` finds for one beat; `Flash` runs it. */
|
|
104
92
|
export const flash = (selector) => __foldkitBrandViewResult((Effect.sync(() => {
|
|
105
93
|
const maybeElement = Option.fromNullishOr(document.querySelector(selector));
|
|
106
94
|
if (Option.isSome(maybeElement)) {
|
|
107
95
|
runFlash(maybeElement.value);
|
|
108
96
|
}
|
|
109
97
|
})), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#flash");
|
|
98
|
+
// COMMAND
|
|
99
|
+
/**
|
|
100
|
+
* Says `text` to screen readers through the page's live region (see `view`). Words said in
|
|
101
|
+
* the same moment are read together, and the region clears first, so the same words twice
|
|
102
|
+
* are read twice. Without a region on the page it does nothing. Reports `CompletedAnnounce`.
|
|
103
|
+
*/
|
|
104
|
+
export const Announce = Command.define("Announce", {
|
|
105
|
+
args: { text: Schema.String },
|
|
106
|
+
messages: [Message.CompletedAnnounce],
|
|
107
|
+
execute: ({ text }) => __foldkitBrandViewResult((announce(text).pipe(Effect.as(Message.CompletedAnnounce()))), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#execute"),
|
|
108
|
+
});
|
|
109
|
+
/**
|
|
110
|
+
* Inverts the element `selector` finds for one beat (`--motion-flash`): the signal that a
|
|
111
|
+
* change was committed. A second flash mid-beat restarts it. With `prefers-reduced-motion`
|
|
112
|
+
* it holds a still 2px ink ring for the beat instead. Return it beside `Announce`, which says
|
|
113
|
+
* what the flash shows. Reports `CompletedFlash`.
|
|
114
|
+
*/
|
|
115
|
+
export const Flash = Command.define("Flash", {
|
|
116
|
+
args: { selector: Schema.String },
|
|
117
|
+
messages: [Message.CompletedFlash],
|
|
118
|
+
execute: ({ selector }) => __foldkitBrandViewResult((flash(selector).pipe(Effect.as(Message.CompletedFlash()))), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#execute~2"),
|
|
119
|
+
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { ANNOUNCER_ATTRIBUTE, Announce, Flash, view } from "./announcer.ts";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/announcer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/announcer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { ANNOUNCER_ATTRIBUTE, Announce, Flash, view } from "./announcer.js";
|
|
@@ -15,7 +15,7 @@ export type ViewConfig = Readonly<{
|
|
|
15
15
|
* interface is in (offline, reconnecting, read-only), for as long as it lasts. One at a
|
|
16
16
|
* time. It is not a live region, since it arrives already holding its sentence, which would
|
|
17
17
|
* go unheard: the application says the state once, when it begins, through the page's live
|
|
18
|
-
* region (`Announcer.
|
|
18
|
+
* region (the `Announcer.Announce` Command). An outcome that comes and goes is a
|
|
19
19
|
* toast; a word beside the control that did it is a FlashWord. */
|
|
20
20
|
export declare const view: <Message>(config: ViewConfig, h: HtmlBuilder<Message>) => Html;
|
|
21
21
|
//# sourceMappingURL=banner.d.ts.map
|
|
@@ -4,7 +4,7 @@ import * as Notice from "../notice/notice.js";
|
|
|
4
4
|
* interface is in (offline, reconnecting, read-only), for as long as it lasts. One at a
|
|
5
5
|
* time. It is not a live region, since it arrives already holding its sentence, which would
|
|
6
6
|
* go unheard: the application says the state once, when it begins, through the page's live
|
|
7
|
-
* region (`Announcer.
|
|
7
|
+
* region (the `Announcer.Announce` Command). An outcome that comes and goes is a
|
|
8
8
|
* toast; a word beside the control that did it is a FlashWord. */
|
|
9
9
|
export const view = (config, h) => {
|
|
10
10
|
const { body, tone = "Error", actions = [] } = config;
|
|
@@ -88,7 +88,7 @@ export const view = (config, h) => {
|
|
|
88
88
|
const top = ChartTable.chartTop(Array.map(points, ({ maybeValue }) => __foldkitBrandViewResult((maybeValue), "@gjermundgaraba-designs/pixel-paper/src/barChart/barChart.js#anonymous~10")), Option.fromNullishOr(max));
|
|
89
89
|
const rootAttributes = [h.Class("chart")];
|
|
90
90
|
return __foldkitBrandViewResult((Array.match(points, {
|
|
91
|
-
onEmpty: () => __foldkitBrandViewResult((EmptyState.view({ title: empty }, h)), "@gjermundgaraba-designs/pixel-paper/src/barChart/barChart.js#onEmpty"),
|
|
91
|
+
onEmpty: () => __foldkitBrandViewResult((EmptyState.view({ title: empty, headingLevel: "Strong" }, h)), "@gjermundgaraba-designs/pixel-paper/src/barChart/barChart.js#onEmpty"),
|
|
92
92
|
onNonEmpty: (present) => __foldkitBrandViewResult((isRows
|
|
93
93
|
? h.div(rootAttributes, [
|
|
94
94
|
tableView(config, category, h),
|
|
@@ -205,7 +205,7 @@ export declare const isDragging: (model: Model) => boolean;
|
|
|
205
205
|
* the board: pause the page's own bindings for them. The board takes Escape before anything
|
|
206
206
|
* else sees it. */
|
|
207
207
|
export declare const isHolding: (model: Model) => boolean;
|
|
208
|
-
/** Says a step of a move through the page's live region (`Announcer.
|
|
208
|
+
/** Says a step of a move through the page's live region (as `Announcer.Announce` does), which is
|
|
209
209
|
* inside the topmost open dialog when there is one. */
|
|
210
210
|
export declare const AnnounceMove: Command.CommandDefinitionWithArgs<"AnnounceMove", {
|
|
211
211
|
text: Schema.String;
|
|
@@ -378,9 +378,6 @@ export declare const cancel: (model: Model) => UpdateReturn;
|
|
|
378
378
|
* that is pressed or held, so no `Moved` arrives for it.
|
|
379
379
|
*/
|
|
380
380
|
export declare const setMovable: (model: Model, isMovable: boolean) => UpdateReturn;
|
|
381
|
-
/** Escape while a card is pressed or held cancels it and goes no further: it is taken on the
|
|
382
|
-
* window, before the page's own key bindings or a dialog see it. */
|
|
383
|
-
export declare const escapeMessage: (event: KeyboardEvent) => Option.Option<Message>;
|
|
384
381
|
/** A held card follows the pointer, the arrows and Space move and drop it, Escape puts it
|
|
385
382
|
* back and the page scrolls near its edges, all from listeners that run only while a card is
|
|
386
383
|
* pressed or held. Lift them into the application's Subscriptions, once per board, with a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../../src/board/board.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,MAAM,EAAgB,MAAM,EAAE,MAAM,EAAgB,MAAM,QAAQ,CAAC;AACnF,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../../src/board/board.ts"],"names":[],"mappings":"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"}
|