@gjermundgaraba-designs/pixel-paper 0.22.2 → 0.24.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 +18 -17
- package/dist/src/adapters/mermaid.d.ts.map +1 -1
- package/dist/src/adapters/mermaid.js +0 -24
- package/dist/src/announcer/announcer.d.ts.map +1 -1
- package/dist/src/announcer/announcer.js +12 -12
- package/dist/src/board/board.d.ts +0 -10
- package/dist/src/board/board.d.ts.map +1 -1
- package/dist/src/board/board.js +36 -64
- package/dist/src/board/index.d.ts +2 -1
- package/dist/src/board/index.d.ts.map +1 -1
- package/dist/src/board/index.js +2 -1
- package/dist/src/button/button.d.ts +1 -1
- package/dist/src/button/button.d.ts.map +1 -1
- package/dist/src/commandPalette/commandPalette.d.ts.map +1 -1
- package/dist/src/commandPalette/commandPalette.js +78 -83
- package/dist/src/commandPalette/keys.d.ts +2 -2
- package/dist/src/commandPalette/keys.d.ts.map +1 -1
- package/dist/src/commandPalette/keys.js +2 -4
- package/dist/src/commandPalette/match.d.ts.map +1 -1
- package/dist/src/commandPalette/match.js +2 -2
- package/dist/src/confirmDialog/confirmDialog.d.ts.map +1 -1
- package/dist/src/confirmDialog/confirmDialog.js +1 -2
- package/dist/src/copyButton/copyButton.d.ts +0 -5
- package/dist/src/copyButton/copyButton.d.ts.map +1 -1
- package/dist/src/copyButton/copyButton.js +2 -2
- package/dist/src/crumbs/crumbs.d.ts +0 -2
- package/dist/src/crumbs/crumbs.d.ts.map +1 -1
- package/dist/src/crumbs/crumbs.js +2 -2
- package/dist/src/dialog/dialog.d.ts +2 -0
- package/dist/src/dialog/dialog.d.ts.map +1 -1
- package/dist/src/dialog/dialog.js +3 -2
- package/dist/src/drag/drag.d.ts +28 -0
- package/dist/src/drag/drag.d.ts.map +1 -1
- package/dist/src/drag/drag.js +39 -17
- package/dist/src/editableTitle/editableTitle.d.ts +4 -4
- package/dist/src/field/field.d.ts +7 -1
- package/dist/src/field/field.d.ts.map +1 -1
- package/dist/src/field/field.js +14 -6
- package/dist/src/fieldset/fieldset.d.ts.map +1 -1
- package/dist/src/fieldset/fieldset.js +2 -4
- package/dist/src/fileTree/fileTree.d.ts.map +1 -1
- package/dist/src/fileTree/fileTree.js +16 -16
- package/dist/src/iconButton/iconButton.d.ts +6 -1
- package/dist/src/iconButton/iconButton.d.ts.map +1 -1
- package/dist/src/iconButton/iconButton.js +7 -2
- package/dist/src/inlineInput/inlineInput.d.ts +4 -0
- package/dist/src/inlineInput/inlineInput.d.ts.map +1 -1
- package/dist/src/inlineInput/inlineInput.js +9 -6
- package/dist/src/keyHelp/index.d.ts +1 -1
- package/dist/src/keyHelp/index.d.ts.map +1 -1
- package/dist/src/keyHelp/index.js +1 -1
- package/dist/src/keyHelp/keyHelp.d.ts +1 -3
- package/dist/src/keyHelp/keyHelp.d.ts.map +1 -1
- package/dist/src/keyHelp/keyHelp.js +3 -6
- package/dist/src/keyHint/keyLabel.d.ts +2 -0
- package/dist/src/keyHint/keyLabel.d.ts.map +1 -1
- package/dist/src/keyHint/keyLabel.js +3 -2
- package/dist/src/lineDiff/diff.d.ts +6 -3
- package/dist/src/lineDiff/diff.d.ts.map +1 -1
- package/dist/src/lineDiff/diff.js +40 -28
- package/dist/src/lineDiff/patch.d.ts.map +1 -1
- package/dist/src/lineDiff/patch.js +18 -13
- package/dist/src/linkState/linkState.d.ts.map +1 -1
- package/dist/src/linkState/linkState.js +1 -7
- package/dist/src/menu/slip.js +2 -2
- package/dist/src/meter/meter.d.ts.map +1 -1
- package/dist/src/meter/meter.js +22 -17
- package/dist/src/notice/notice.d.ts +2 -0
- package/dist/src/notice/notice.d.ts.map +1 -1
- package/dist/src/notice/notice.js +2 -1
- package/dist/src/panel/panel.d.ts +3 -0
- package/dist/src/panel/panel.d.ts.map +1 -1
- package/dist/src/panel/panel.js +2 -1
- package/dist/src/passwordField/passwordField.d.ts.map +1 -1
- package/dist/src/passwordField/passwordField.js +3 -7
- package/dist/src/rail/index.d.ts +1 -1
- package/dist/src/rail/index.d.ts.map +1 -1
- package/dist/src/rail/index.js +1 -1
- package/dist/src/rail/rail.d.ts +95 -20
- package/dist/src/rail/rail.d.ts.map +1 -1
- package/dist/src/rail/rail.js +103 -46
- package/dist/src/reel/layout.d.ts +9 -5
- package/dist/src/reel/layout.d.ts.map +1 -1
- package/dist/src/reel/layout.js +11 -5
- package/dist/src/reel/reel.d.ts.map +1 -1
- package/dist/src/reel/reel.js +122 -68
- package/dist/src/scheme/scheme.d.ts +1 -1
- package/dist/src/scheme/scheme.d.ts.map +1 -1
- package/dist/src/segmented/segmented.d.ts +0 -3
- package/dist/src/segmented/segmented.d.ts.map +1 -1
- package/dist/src/segmented/segmented.js +1 -6
- package/dist/src/selectField/selectField.d.ts.map +1 -1
- package/dist/src/selectField/selectField.js +3 -10
- package/dist/src/sortable/index.d.ts +1 -1
- package/dist/src/sortable/index.d.ts.map +1 -1
- package/dist/src/sortable/index.js +1 -1
- package/dist/src/sortable/sortable.d.ts +39 -40
- package/dist/src/sortable/sortable.d.ts.map +1 -1
- package/dist/src/sortable/sortable.js +14 -31
- package/dist/src/stack/stack.d.ts +1 -1
- package/dist/src/stack/stack.d.ts.map +1 -1
- package/dist/src/stack/stack.js +1 -1
- package/dist/src/tabs/tabs.d.ts +1 -1
- package/dist/src/tabs/tabs.d.ts.map +1 -1
- package/dist/src/tabs/tabs.js +1 -1
- package/dist/src/textareaField/textareaField.d.ts.map +1 -1
- package/dist/src/textareaField/textareaField.js +3 -7
- package/dist/src/time/time.d.ts.map +1 -1
- package/dist/src/time/time.js +4 -2
- package/dist/src/tips/index.d.ts +1 -1
- package/dist/src/tips/index.d.ts.map +1 -1
- package/dist/src/tips/tips.d.ts +30 -1
- package/dist/src/tips/tips.d.ts.map +1 -1
- package/dist/src/tips/tips.js +53 -31
- package/dist/src/toast/index.d.ts +1 -1
- package/dist/src/toast/index.d.ts.map +1 -1
- package/dist/src/toast/toast.d.ts +5 -5
- package/dist/src/toast/toast.d.ts.map +1 -1
- package/dist/src/toast/toast.js +1 -6
- package/dist/src/toolbar/toolbar.d.ts +0 -3
- package/dist/src/toolbar/toolbar.d.ts.map +1 -1
- package/dist/src/toolbar/toolbar.js +1 -2
- package/package.json +1 -1
- package/src/adapters/mermaid.ts +0 -24
- package/src/announcer/announcer.ts +13 -17
- package/src/board/board.ts +32 -80
- package/src/board/index.ts +1 -1
- package/src/button/button.ts +1 -1
- package/src/commandPalette/commandPalette.ts +21 -38
- package/src/commandPalette/keys.ts +2 -4
- package/src/commandPalette/match.ts +3 -3
- package/src/confirmDialog/confirmDialog.ts +5 -3
- package/src/copyButton/copyButton.ts +2 -5
- package/src/crumbs/crumbs.ts +2 -4
- package/src/dialog/dialog.ts +3 -2
- package/src/drag/drag.ts +54 -5
- package/src/field/field.ts +29 -7
- package/src/fieldset/fieldset.ts +2 -4
- package/src/fileTree/fileTree.ts +10 -12
- package/src/iconButton/iconButton.ts +19 -2
- package/src/inlineInput/inlineInput.ts +11 -2
- package/src/keyHelp/index.ts +0 -1
- package/src/keyHelp/keyHelp.ts +3 -9
- package/src/keyHint/keyLabel.ts +5 -2
- package/src/lineDiff/diff.ts +70 -54
- package/src/lineDiff/patch.ts +27 -14
- package/src/linkState/linkState.ts +1 -8
- package/src/menu/slip.ts +2 -2
- package/src/meter/meter.ts +25 -15
- package/src/notice/notice.ts +2 -1
- package/src/panel/panel.ts +5 -0
- package/src/passwordField/passwordField.ts +3 -7
- package/src/pixel-paper.css +200 -134
- package/src/rail/index.ts +2 -0
- package/src/rail/rail.ts +124 -44
- package/src/reel/layout.ts +14 -7
- package/src/reel/reel.ts +132 -63
- package/src/scheme/scheme.ts +1 -1
- package/src/segmented/segmented.ts +1 -9
- package/src/selectField/selectField.ts +3 -10
- package/src/sortable/index.ts +0 -1
- package/src/sortable/sortable.ts +35 -54
- package/src/stack/stack.ts +1 -1
- package/src/tabs/tabs.ts +1 -1
- package/src/textareaField/textareaField.ts +3 -7
- package/src/time/time.ts +6 -7
- package/src/tips/index.ts +1 -1
- package/src/tips/tips.ts +71 -35
- package/src/toast/index.ts +1 -1
- package/src/toast/toast.ts +2 -8
- package/src/tokens.css +7 -0
- package/src/toolbar/toolbar.ts +0 -5
- package/dist/src/inlineInput/inline.d.ts +0 -6
- package/dist/src/inlineInput/inline.d.ts.map +0 -1
- package/dist/src/inlineInput/inline.js +0 -7
- package/src/inlineInput/inline.ts +0 -13
package/DESIGN.md
CHANGED
|
@@ -208,6 +208,7 @@ spacing:
|
|
|
208
208
|
deck-max: "1160px"
|
|
209
209
|
rail: "272px"
|
|
210
210
|
rail-drawer: "320px"
|
|
211
|
+
rail-folded: "64px"
|
|
211
212
|
card-max: "680px"
|
|
212
213
|
card-narrow: "440px"
|
|
213
214
|
row-min: "34px"
|
|
@@ -822,7 +823,7 @@ Two neutral palettes over one set of tokens, with three meaning hues shared betw
|
|
|
822
823
|
### Neutral
|
|
823
824
|
|
|
824
825
|
- **Printer Ink** (`{colors.ink}` paper / `{colors.ink-plum}` plum): all text, every 2px border, every inverted fill (title bars, primary button, current nav segment, pressed filter chip, current file, current menu item, the palette cursor row, the cursor card of a board, a lit cell, the version in force, a done button, a selected row's check cell, the ok glyph box), chart cells and the line chart's line, the keyboard ring, the scrollbar thumb, the text-selection wash.
|
|
825
|
-
- **Paper White** (`{colors.paper}` paper / `{colors.paper-plum}` plum): the panel, card, sheet, menu, button and chip surface; the text on inverted fills; inside a board's cursor card, what prints muted on paper prints in paper at 0.75.
|
|
826
|
+
- **Paper White** (`{colors.paper}` paper / `{colors.paper-plum}` plum): the panel, card, sheet, menu, button and chip surface; the text on inverted fills; inside a board's cursor card, what prints muted on paper prints in paper at 0.75 (`--fade-on-ink`).
|
|
826
827
|
- **Ground Grey** (`{colors.ground}` paper / `{colors.ground-plum}` plum): the page behind the panels, always under the dither (`{colors.dither-base}` cells with `{colors.dither-dot}` dots; `{colors.dither-base-plum}` / `{colors.dither-dot-plum}` on plum). Also the scrollbar track.
|
|
827
828
|
- **Head Tint** (`{colors.head}` paper / `{colors.head-plum}` plum): the one tonal step off the paper: table heads, every hover (row, board card, menu item, file, table row), inline code, the code surface and secret, an added diff line and a folded run, an unlit cell, the done chip, the quiet notice's glyph box, the nav count, a drop target, a picture's placeholder under the picture, an unlit chart cell. Text on a head fill uses **On Head** (`{colors.on-head}` / `{colors.on-head-plum}`), a separate token because Muted Ink misses AA on plum's head.
|
|
828
829
|
- **Rule Grey** (`{colors.rule}` paper / `{colors.rule-plum}` plum): every 1px division: table, property, menu, history and keymap rows, meter rows, the panel foot, outline and file-tree guides, the diff's number gutter, the tag border, the quiet notice's border, the divisions between stats, the line chart's axes, the select-all head's foot.
|
|
@@ -863,7 +864,7 @@ The working body is 13px on 1.45; the reading body is 15px on 1.65. There is no
|
|
|
863
864
|
Working scale:
|
|
864
865
|
|
|
865
866
|
- **Wordmark** (400, 18px `--type-wordmark`, -0.02em): the product name in a top bar, followed by an 8×15px block cursor that blinks four times. **Wordmark wall** (22px `--type-wordmark-wall`) beside the glyph box on a wall.
|
|
866
|
-
- **Title bar** (400, 13px `--type-body`, uppercase, 0.08em `--tracking-title`; 0.06em `--tracking-head` in cards): the inverted heading of every panel and card. Its meta sits at 11px at 75% opacity, right-aligned.
|
|
867
|
+
- **Title bar** (400, 13px `--type-body`, uppercase, 0.08em `--tracking-title`; 0.06em `--tracking-head` in cards): the inverted heading of every panel and card. Its meta sits at 11px at 75% opacity (`--fade-on-ink`), right-aligned.
|
|
867
868
|
- **Name** (400, 14px `--type-name`, uppercase, 0.04em): a group's name in an outline, a meter's name.
|
|
868
869
|
- **Control** (400, 12px `--type-control`, uppercase, 0.04em): buttons, nav segments, menu items, palette rows, text buttons, a meter's percentage; the empty-state and drop-zone headline at 0.08em (`--tracking-title`).
|
|
869
870
|
- **Figure** (400, 30px `--type-figure` / 1.1, tabular figures): a stat's value, its unit beside it at 12px uppercase muted; 22px (`--type-figure-narrow`) at 900px and under.
|
|
@@ -921,11 +922,11 @@ The Deck is one centred column, `max-width: 1160px` (`--deck`), padded 24px top,
|
|
|
921
922
|
|
|
922
923
|
**Reel:** a row of columns of windows that fills whatever holds it and scrolls sideways when the columns are wider than it, and down where a column's panes need more than its height, `--reel-gutter` (16px) between columns, between a column's panes and at all four edges (with no column head, the gutter above the panes is the reel's own). A column's width is a share of the reel's (`width × (view − gutter) − gutter`, so columns whose shares add up to one fill it exactly), never under `--reel-column-min` (280px); a column is the reel's height less the gutters above and below it, and a pane's height its share of its column by weight, never under `--reel-pane-min` (120px), a column of more panes than fit growing taller. The shares a width steps through (a third, half, two thirds, the whole) are the component's, in its code, not tokens: they are the words it says as much as the sizes it prints.
|
|
923
924
|
|
|
924
|
-
**Rail:** a column of panels beside the deck (`--rail`, 272px, then the deck centred in what is left), fixed from the top bar's lower edge (hung from it by anchor positioning, like the stage) to the window's bottom,
|
|
925
|
+
**Rail:** a column of panels beside the deck (`--rail`, 272px, then the deck centred in what is left), fixed from the top bar's lower edge (hung from it by anchor positioning, like the stage) to the window's bottom, padded `24px 10px 24px 20px` so its panels' shadows print. **Its top stays put:** it is a column in which what comes before its last panel keeps its own height and the last panel takes the room left, at most its own height, its title bar, shelf and foot staying while its body scrolls (the padded body, or a flush body's list): a sidebar is one panel whose bar is its head, the field that picks what it lists on the shelf under the bar, its rows scrolling, its action in the foot. A short list leaves its panel at its own height. The rail scrolls as a whole only when what comes before its last panel is taller than the window, and its last panel then has no room of its own. **The reader can hide it:** the toggle in the first panel's bar folds the rail to that bar stood on end on the ground, `--rail-folded` (64px) wide, an ink tab with the toggle at its top and the title and count reading down (`writing-mode: vertical-rl`), the bar's actions, the rest of the panel, the other panels and the splitter gone; the deck's padding and a bench's left read the strip's width. Folding and unfolding move the width in three steps over `--motion-card`, as the drawer enters, and at once under reduced motion or while the splitter is dragged (a transition, so it never plays on load; a width or a fold the parent restores after hydration steps once, and an arrow on the splitter steps its 16px too). Hidden is a preference that lasts, not an overlay, and only above 900px: the drawer opens whole. The reader sizes it from its right edge, between 220px (the least its panels still fit: a tree's row keeps its glyph, a dot and a few words of its title) and 480px (the most, which leaves the deck at least 421px of a 901px window, the narrowest the rail stands in, more than a phone has); the width they give is set as `--railed-width` on `.railed`, so the page's padding and the rail's width move together, and a bench beside it follows; without one they read `--rail`. **The width is not inherited:** `--railed-width` is registered with `inherits: false` and handed by name to the rail, its splitter and a bench (`.railed > :is(.rail, .rail-resize, .bench)`), so a width that changes under a dragged splitter restyles those boxes and the shell, not every row inside them. A rule of the adopter's that reads `--rail` inside the rail reads the token, not the reader's width. At 900px and under it leaves the page: the deck starts with a small button (the `sidebar` glyph and the rail's name) that opens the same element as a drawer, `--rail-drawer` (320px) wide whatever width the reader gave the rail, since a width chosen beside a page is no measure of a drawer over a phone.
|
|
925
926
|
|
|
926
927
|
Frames with no surface of their own (Deck, Wall, Stage, Bench) are described here rather than among the components.
|
|
927
928
|
|
|
928
|
-
**Bench:** a page whose work is the window: a task's terminals and editors side by side, which a reading column would cramp and a frame of its own would scroll twice. Fixed from the top bar's lower edge (hung from it by anchor positioning, like the stage and the rail) to the status bar, the window wide, or from the rail's edge beside a rail (a fixed box takes nothing from `.railed`'s padding, so the bench reads
|
|
929
|
+
**Bench:** a page whose work is the window: a task's terminals and editors side by side, which a reading column would cramp and a frame of its own would scroll twice. Fixed from the top bar's lower edge (hung from it by anchor positioning, like the stage and the rail) to the status bar, the window wide, or from the rail's edge beside a rail (a fixed box takes nothing from `.railed`'s padding, so the bench reads the rail's width itself and follows the reader's), and from the left edge at 900px and under, where the rail is a drawer. It is a grid of two rows: the head (a PageHead, notices, the rail's open button first) at its own height, padded 24px at the top and 16px at the sides with 24px between its parts, and the work in all the height left, where a reel takes 100% of it. **The work keeps a floor:** `--bench-work-min` (152px, one pane at `--reel-pane-min` between a reel's gutters, the least a reel still shows a window in), whatever the head holds, or all of the bench but 64px on a window too short for both (a bench under 216px), the head's padding and a small button, so the head's first line, the rail's open button at 900px and under, never vanishes. A head taller than what the floor leaves it (a PageHead, a status and two notices on a landscape phone) stops there and scrolls itself, so all of it can be reached while the work stays where it is: the rows are `minmax(0, max-content) minmax(min(var(--bench-work-min), 100% - 64px), 1fr)`. That was chosen over scrolling the whole bench, which would scroll a page around a reel that scrolls itself (a wheel over the reel, held in it, would never reach the page) and push the work off the window to read the head: the head is read once, the work is what the reader keeps in view. A scroller clips what prints past it, so the head pads its foot by 6px (a button's 3px shadow, a focus ring's 4px) and hands the 6px back in a negative margin, the work starting where it would. A keyboard scrolls the head with the arrows from a control in it, and a screen reader reads it whole. The head's 16px is a reel's gutter (`--reel-gutter`), so the head's edge lines up with the windows' and the reel's own top gutter is the space between them. The page does not scroll: the work scrolls itself where it must (a reel's columns sideways, and down where a column is taller). While a dialog in its work is open the bench is lifted over the rail and the bars (see Dialog). The Deck stays the frame for pages that read; the Stage, for a canvas with no head.
|
|
929
930
|
|
|
930
931
|
**Stage:** the ground itself for a canvas: fixed from the top bar's lower edge (it hangs from the bar by anchor positioning, so a phone's two-line bar fits) to the window's edges. It does not scroll; what is on it pans. A dialog in its canvas lifts it over the bars while it is open (see Dialog). Its floating tools sit 16px in from the left edge, centred (along the bottom, centred, on a phone); its bar sits 16px down from the top, centred, wrapping when narrow.
|
|
931
932
|
|
|
@@ -935,7 +936,7 @@ Breakpoints: 600px (phone; the page title steps to 18px, stats two across, the w
|
|
|
935
936
|
|
|
936
937
|
### Named Rules
|
|
937
938
|
|
|
938
|
-
**The Panel Scroll Rule.** The page scrolls; a panel does not, except a board column above 900px, a card body inside a dialog, the rail, a window's body (a window is sized by the canvas, not by its content), and a reel, which scrolls sideways, and down where a column is taller than it, its windows sized by its columns. A stage never scrolls, and nor does a bench: its work scrolls itself, and its head scrolls only when the window leaves it less than it holds once the work has its floor. A wide table, a long code line or a run of bar columns scrolls horizontally inside its own panel and never widens the deck.
|
|
939
|
+
**The Panel Scroll Rule.** The page scrolls; a panel does not, except a board column above 900px, a card body inside a dialog, the body of the rail's last panel (under its bar, its shelf and its foot, which stay; the rail itself only when what comes before that panel is taller than the window), a window's body (a window is sized by the canvas, not by its content), and a reel, which scrolls sideways, and down where a column is taller than it, its windows sized by its columns. A stage never scrolls, and nor does a bench: its work scrolls itself, and its head scrolls only when the window leaves it less than it holds once the work has its floor. A wide table, a long code line or a run of bar columns scrolls horizontally inside its own panel and never widens the deck.
|
|
939
940
|
|
|
940
941
|
**The 24px Target Rule.** Every target is at least 24px square. A control drawn smaller (a tab, a glyph-switch option, a filter chip, a row toggle, a clickable mark or tag, a text button, a crumb, an error-summary link) keeps its look and takes the pointer over an invisible 24px square centred on it; an icon button never gives up its 24px to a full line (a tag beside it clips first), and links in a list stand 24px apart so no two squares overlap.
|
|
941
942
|
|
|
@@ -979,7 +980,7 @@ Icons are pixel glyphs: filled `<rect>` cells on a 12×12 `viewBox`, `fill="curr
|
|
|
979
980
|
|
|
980
981
|
## Components
|
|
981
982
|
|
|
982
|
-
Every component below is a namespace exported from the package root (`import { Button, ConfirmDialog } from "@gjermundgaraba-designs/pixel-paper"`) and a set of classes in `pixel-paper.css`. Each is one of two kinds. A **View** is a stateless render helper, `X.view(config, h)`: the caller owns any state and passes its own Messages in the config (`onClick`, `onToggle`, `onInput`). A **Submodel** keeps state of its own: `X.Model`, `X.Message`, `X.init(config)`, `X.update`, and a `view` built with `defineView` and embedded with `h.submodel`, with its per-render `viewInputs`; it reports what the parent must act on as an `X.OutMessage`, folded in with `Update.foldChild`, and exports the accessors something reads (`X.id`, `Dialog.isOpen`) and update functions (`X.open`, `X.close`) for the parent to call. Each entry says what the component is for, its kind where it is a Submodel, the markup it renders, which is the contract for use without Foldkit, and how it behaves. Its config or view inputs, its OutMessages, its accessors and its Commands are in its types and their doc comments (`src/<name>/<name>.ts`), not restated here.
|
|
983
|
+
Every component below is a namespace exported from the package root (`import { Button, ConfirmDialog } from "@gjermundgaraba-designs/pixel-paper"`) and a set of classes in `pixel-paper.css`. Each is one of two kinds. A **View** is a stateless render helper, `X.view(config, h)`: the caller owns any state and passes its own Messages in the config (`onClick`, `onToggle`, `onInput`). A **Submodel** keeps state of its own: `X.Model`, `X.Message`, `X.init(config)`, `X.update`, and a `view` built with `defineView` and embedded with `h.submodel`, with its per-render `viewInputs`; it reports what the parent must act on as an `X.OutMessage`, folded in with `Update.foldChild`, and exports the accessors something reads (`X.id`, `Dialog.isOpen`) and update functions (`X.open`, `X.close`) for the parent to call. Each entry says what the component is for, its kind where it is a Submodel, the markup it renders, which is the contract for use without Foldkit, and how it behaves. Its config or view inputs, its OutMessages, its accessors and its Commands are in its types and their doc comments (`src/<name>/<name>.ts`), not restated here. A view takes `attributes` for its root where a caller passes them; "Building components" below holds the rules they all follow.
|
|
983
984
|
|
|
984
985
|
```ts
|
|
985
986
|
import { Button, ConfirmDialog } from "@gjermundgaraba-designs/pixel-paper";
|
|
@@ -1018,7 +1019,7 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
|
|
|
1018
1019
|
- **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.
|
|
1019
1020
|
- **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.
|
|
1020
1021
|
- **Key hint (`keyHint`):** the key that does the same, as its Foldkit key binding names it (`"N"`, `"U"`, `"Mod+K"`), printed after the words as `kbd.key-hint[aria-hidden="true"]`: 8px clear of them, the 10px pixel face with no case change or tracking, in the button's own colour at 0.65. It is for the eye; the button's name stays its words. A done button drops it.
|
|
1021
|
-
- **TextButton** → `button.textbtn`: the control register with no box and a dotted underline on hover ("sign out"). **IconButton** → `button.iconbtn[aria-label][data-tip]` (`.quiet`): a 24px square glyph button whose `label`, required, is both its accessible name and its tip; quiet is Muted Ink until hovered. A Secret or a PropsList row that copies carries a CopyButton (see Code surface).
|
|
1022
|
+
- **TextButton** → `button.textbtn`: the control register with no box and a dotted underline on hover ("sign out"). **IconButton** → `button.iconbtn[aria-label][data-tip]` (`.quiet`): a 24px square glyph button whose `label`, required, is both its accessible name and its tip; quiet is Muted Ink until hovered. Its `keyHint`, with no words to print beside, goes in `data-tip-key` (the key as the help prints it) and prints in its tip after the label. A Secret or a PropsList row that copies carries a CopyButton (see Code surface).
|
|
1022
1023
|
|
|
1023
1024
|
### Navigation
|
|
1024
1025
|
|
|
@@ -1026,7 +1027,7 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
|
|
|
1026
1027
|
- **Segmented** → `div.nav[role="group"] > button[aria-pressed]`: a choice of one printed as the same strip. When every option carries a `glyph` it prints as `div.switch > button[aria-pressed][aria-label]`, the strip holding 26×22px glyph cells (a layout, a density), each option's label its name and its tip. **Scheme.SchemeSwitch** is a Submodel printing a Segmented of system, paper and plum that remembers the choice (see Palette below).
|
|
1027
1028
|
- **Tabs** (Submodel over `@foldkit/ui`'s Tabs; the caller keeps the tab on show) → `div.tabbed > div.nav.tabs[role="tablist"] > button[role="tab"][aria-selected]` plus `div.tab-panel[role="tabpanel"]`: the strip at 11px with `4px 12px` segments, a count spaced as NavStrip's; arrows, Home and End move and select. At the top of a flush panel the strip sits inside the panel's padding.
|
|
1028
1029
|
- **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.
|
|
1029
|
-
- **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.
|
|
1030
|
+
- **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 (`--fade-on-ink`), never tinted by hover; the ring sits 4px inside the line, in paper on the current one.
|
|
1030
1031
|
- **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.
|
|
1031
1032
|
- **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`.
|
|
1032
1033
|
- **StatusBar** (a Mount measures it, reporting through `toParentMessage`) → `footer.statusbar > div.statusbar-state + p.statusbar-keys`: the quiet line along the bottom of an interface, the page's footer (one per page, after the rail and the deck, outside `main`). Fixed to the bottom edge on paper over a 1px rule, in the pixel face at the state size in Muted Ink: the status at the left (a LinkState, a sync word), the keys at the right, each a `kbd.key-hint` in ink (printed as the help prints it, ⌘K or Ctrl+K by platform) and its word, plain text and not pressable. Narrow, the keys wrap under the status; nothing hides. It keeps `--statusbar-h` on the page at its height (25px, one line, until it is measured), like the top bar's `--topbar-h`: the page pads its end by it, a scrolled-to anchor stays clear of it, and the rail and its splitter, the stage, a bench, a Banner and the toast stack all stop above it.
|
|
@@ -1054,13 +1055,13 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
|
|
|
1054
1055
|
|
|
1055
1056
|
### Panels
|
|
1056
1057
|
|
|
1057
|
-
- **Panel** → `section.panel[aria-labelledby]` (`[aria-label]` with `name`) `> header.panel-tb > span.panel-lead + h2 + span.meta`, then `div.panel-body` (or the body directly when `isFlush`), then `div.panel-foot`. The section is named by its title, or by `name` where the title repeats among its siblings (panels titled by a kind, one per account), so each landmark keeps a name of its own. It is keyed by its `id`, so a panel among siblings that come and go keeps its own element, and a panel under another id is another element: one that replaces another in the same place is built anew (a live region inside it is inserted, not kept), so keep one panel under one id across states that should share it. Square, 2px ink border, Paper White, panel shadow. The title bar is inverted Departure Mono 13px uppercase 0.08em (`--tracking-title`), `padding: 6px 12px`; its meta is 11px at 75% opacity. A control in a title bar (a panel's, a window's or a card's) rings in paper, 2px inside its own edge, because the ink ring would vanish on the ink bar. The body pads 16px with a 14px grid gap; the foot pads `10px 12px` over a 1px rule, with a `.push` slot that goes right. `actions` puts small Buttons and IconButtons in `span.panel-acts` (6px apart) at the title bar's right end, after the meta; the bar trims to `3px 3px 3px 12px` at 31px or more. `lead` puts a quiet IconButton in `span.panel-lead` before the title (a Sortable's grip), and the bar trims its left to 3px the same way; a quiet icon button there prints in paper, hovered too, and held at 60
|
|
1058
|
+
- **Panel** → `section.panel[aria-labelledby]` (`[aria-label]` with `name`) `> header.panel-tb > span.panel-lead + h2 + span.meta`, then `div.panel-shelf`, then `div.panel-body` (or the body directly when `isFlush`), then `div.panel-foot`. The section is named by its title, or by `name` where the title repeats among its siblings (panels titled by a kind, one per account), so each landmark keeps a name of its own. It is keyed by its `id`, so a panel among siblings that come and go keeps its own element, and a panel under another id is another element: one that replaces another in the same place is built anew (a live region inside it is inserted, not kept), so keep one panel under one id across states that should share it. Square, 2px ink border, Paper White, panel shadow. The title bar is inverted Departure Mono 13px uppercase 0.08em (`--tracking-title`), `padding: 6px 12px`; its meta is 11px at 75% opacity (`--fade-on-ink`). 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. `shelf` is a strip on Head Tint between the bar and the body, `10px 12px 12px` over a 1px rule, for a field that picks what the body shows (an area over its tasks in a rail); a field's label there prints in On Head, since Muted Ink misses AA on the tint. `actions` puts small Buttons and IconButtons in `span.panel-acts` (6px apart) at the title bar's right end, after the meta; the bar trims to `3px 3px 3px 12px` at 31px or more. `lead` puts a quiet IconButton in `span.panel-lead` before the title (a Sortable's grip), and the bar trims its left to 3px the same way; a quiet icon button there prints in paper, hovered too, and held at 60% (`--fade-held`). 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% (`--fade-held`).
|
|
1058
1059
|
- **Deck** → `main.deck#main`: the column of panels.
|
|
1059
1060
|
- **Bench** → `main.bench#main > div.bench-head + div.bench-work`: see Layout. `head` and `body` are both the caller's; beside a Rail it is what `toDeckView` returns, with the open button first in its head.
|
|
1060
1061
|
- **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.
|
|
1061
1062
|
- **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).
|
|
1062
1063
|
- **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.
|
|
1063
|
-
- **Rail** (Submodel) → `div.railed > main.deck#main + aside.rail[popover="auto"][aria-label] + div.rail-resize` (a `main.bench#main` in the deck's place for a page whose work is the window): the rail's panels (a tree, places, filters) beside the deck's, after it in the markup so the page's main content and its h1 come first in reading order; the stylesheet fixes the rail at the left under the top bar. One element serves both layouts, so nothing in it renders twice or loses its state: at rest it stands there (its `popover` attribute is inert until opened), and at 900px and under it is hidden and `span.rail-open > button.btn.small[aria-expanded]` at the top of the deck opens it as a drawer: a slice of the dithered ground, `min(var(--rail-drawer), 100vw - 40px)` (320px) wide from the left edge, whatever width the reader gave the rail, closed by a 2px ink rule and the 8px card shadow, entering from the left in three steps over a veil. Its head (`div.rail-head`: the name in the label register and a close IconButton) shows only in the drawer. Opening focuses its first control; Escape, a click on the veil, the close glyph or focus leaving closes it, and all but the last return focus to the button. Widening the window past 900px closes it. The parent reads whether the drawer is open with `Rail.isOpen`, to hold its own page keys while the drawer covers the page. **Its width is the reader's:** after the aside, `div.rail-resize[role="separator"][aria-orientation="vertical"]` ("Width of <label>", `aria-controls` the rail, its width as `aria-valuenow` between `aria-valuemin` 220 and `aria-valuemax` 480 and as "300 pixels" in `aria-valuetext`) is a 12px strip from the rail's right edge outward, over the page's margin and never over the rail's own scrollbar, fixed beside the rail rather than in it (the rail scrolls, and the drawer's first Tab stop is its first control), a `col-resize` cursor showing a 2px ink line down the edge while hovered or focused, hidden with the rail at 900px and under. A pointer drags it, the rail printing the new width as it moves, and Escape or the browser's cancel puts it back; the side arrows, unmodified, step it 16px, a modified arrow being the application's. Let go where it changes the width, or stepped, it reports `ResizedRail` with the width in whole pixels within the bounds, and the parent keeps it and passes it back as `maybeWidth`: the rail holds no width of its own, as the reel holds no layout. Until one is given, the splitter says 272px, a copy of `--rail` in the component's code: the server render needs the number before anything is measured, and the package is compiled from `src/` by `tsc` alone, which the specimen's token scanner (a dev dependency, fed through Vite's `?raw`) cannot reach without a build step of its own. The parent keeps it per client, as it keeps focus and scroll, since a width suits a window, not a task. The splitter's handlers are the reel's (`src/drag`), and the rail's Subscriptions, which follow a dragged splitter and take Escape, are lifted behind a key prefix of the parent's. A file tree flush in a rail panel (or any panel) drops its own border.
|
|
1064
|
+
- **Rail** (Submodel) → `div.railed > main.deck#main + aside.rail[popover="auto"][aria-label] + div.rail-resize` (a `main.bench#main` in the deck's place for a page whose work is the window): the rail's panels (a tree, places, filters) beside the deck's, after it in the markup so the page's main content and its h1 come first in reading order; the stylesheet fixes the rail at the left under the top bar. One element serves both layouts, so nothing in it renders twice or loses its state: at rest it stands there (its `popover` attribute is inert until opened), and at 900px and under it is hidden and `span.rail-open > button.btn.small[aria-expanded]` at the top of the deck opens it as a drawer: a slice of the dithered ground, `min(var(--rail-drawer), 100vw - 40px)` (320px) wide from the left edge, whatever width the reader gave the rail, closed by a 2px ink rule and the 8px card shadow, entering from the left in three steps over a veil. Its head (`div.rail-head`: the name in the label register and a close IconButton) shows only in the drawer. Opening focuses its first control; Escape, a click on the veil, the close glyph or focus leaving closes it, and all but the last return focus to the button. Widening the window past 900px closes it. The parent reads whether the drawer is open with `Rail.isOpen`, to hold its own page keys while the drawer covers the page. **Its width is the reader's:** after the aside, `div.rail-resize[role="separator"][aria-orientation="vertical"]` ("Width of <label>", `aria-controls` the rail, its width as `aria-valuenow` between `aria-valuemin` 220 and `aria-valuemax` 480 and as "300 pixels" in `aria-valuetext`) is a 12px strip from the rail's right edge outward, over the page's margin and never over the rail's own scrollbar, fixed beside the rail rather than in it (a panel's body in the rail scrolls, and the drawer's first Tab stop is its first control), a `col-resize` cursor showing a 2px ink line down the edge while hovered or focused, hidden with the rail at 900px and under. A pointer drags it, the rail printing the new width as it moves, and Escape or the browser's cancel puts it back; the side arrows, unmodified, step it 16px, a modified arrow being the application's. Let go where it changes the width, or stepped, it reports `ResizedRail` with the width in whole pixels within the bounds, and the parent keeps it and passes it back as `maybeWidth`: the rail holds no width of its own, as the reel holds no layout. Until one is given, the splitter says 272px, a copy of `--rail` in the component's code: the server render needs the number before anything is measured, and the package is compiled from `src/` by `tsc` alone, which the specimen's token scanner (a dev dependency, fed through Vite's `?raw`) cannot reach without a build step of its own. Its test reads the stylesheet and holds that copy, and the 900px the drawer follows, to it. The parent keeps it per client, as it keeps focus and scroll, since a width suits a window, not a task. The splitter's handlers are the reel's (`src/drag`), and the rail's Subscriptions, which follow a dragged splitter and take Escape, are lifted behind a key prefix of the parent's. A file tree flush in a rail panel (or any panel) drops its own border. A panel's title in the rail stays on one line and ends in an ellipsis rather than wrapping the bar. **Its top is pinned and it folds** (see Layout): the rail hands `toAsideView` its toggle, `span.rail-toggle > button.iconbtn.quiet[aria-expanded][aria-controls]` with the `sidebar` glyph, "Hide <label>" or "Show <label>", as it hands `toDeckView` the open button, and the first panel prints it as its `lead`, so the folded tab finds it where the stylesheet looks. It is chrome, not a mark: never amber. Pressed, it reports `ToggledRail`, and the parent flips whether it keeps the rail hidden and passes it back as `isHidden`, kept per client as the width is; the rail holds neither. A key of the parent's does the same through `Rail.toggle`, and its key prints in the toggle's tip (`maybeToggleKeyHint`, in Button's `keyHint` shape). Hidden, `div.railed.folded` drops the reader's width for `--rail-folded`, the splitter leaves the markup, and the first panel (`.rail-head + .panel`) prints only its bar on end; everything else in the rail is `display: none`, so out of the tab order and the accessibility tree, and the aside, still named by its label, holds the toggle and the title. Folding or unfolding with focus in the rail or on its splitter puts focus on the toggle (one element in both states, so a pointer's press keeps it there too); focus elsewhere stays where it is. At 900px and under the drawer opens whole whether the rail is hidden or not, and prints no toggle (`.panel-lead` holding it is hidden, the bar keeping its own padding): a drawer opens and closes but does not fold, and its first Tab stop passes the toggle over. **Where the design degrades:** the fold is CSS over the first panel, so a rail whose first panel does not lead with the toggle folds to a bar with no way back but the parent's key, and a rail that does not print the toggle at all can be hidden by nothing but that key; put the toggle in the first panel's `lead`.
|
|
1064
1065
|
- **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.
|
|
1065
1066
|
- **Window** → `section.panel.window (.flush) > header.panel-tb + div.window-body + div.panel-foot`: a Panel for a stage or a reel, as tall and wide as whatever places it (and moved by it, if anything moves it). Its title bar is the panel's, trimmed to hold tools: 30px tall (`3px 3px 3px 10px`, its left trimmed to 3px under a `lead`), centred, with an optional `lead` (`span.panel-lead`, a quiet IconButton: a Reel's grip), an optional kind glyph, the title (clipped with an ellipsis), a status chip (its border turns paper so it reads on ink), tools and a close IconButton, both in the bar's paper. Tools a child Submodel hands back come as one element, `span.window-tools`, which is no box of its own (`display: contents`), so its buttons sit in the bar as the others do. The body scrolls inside (`16px`, a 14px grid); flush, what fills it (a code surface, a file tree, a diff) drops its border and fills it. A foot is a panel foot; a notice there takes the whole line.
|
|
1066
1067
|
- **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).
|
|
@@ -1091,19 +1092,19 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
|
|
|
1091
1092
|
|
|
1092
1093
|
### Tips and toolbars
|
|
1093
1094
|
|
|
1094
|
-
**Tips** (Submodel; `Tips.subscriptions(keyPrefix)` follow the pointer, focus and keys across the page, keyed behind the parent's prefix: `Tips.subscriptions("tip")` is keyed `tipPointerAndFocus` and `tipKeys`) → one `div.tip[popover="manual"][aria-hidden="true"]` for the page, rendered once beside the Announcer, which shows the words of any element carrying `data-tip
|
|
1095
|
+
**Tips** (Submodel; `Tips.subscriptions(keyPrefix)` follow the pointer, focus and keys across the page, keyed behind the parent's prefix: `Tips.subscriptions("tip")` is keyed `tipPointerAndFocus` and `tipKeys`) → one `div.tip[popover="manual"][aria-hidden="true"]` for the page, rendered once beside the Announcer, which shows the words of any element carrying `data-tip`, then the key in its `data-tip-key` where it has one, as a button's key hint prints (the 10px face, no case change, at 0.75): a label, not a surface, so flat Printer Ink with Paper White text and no print shadow, ringed by a 2px paper outline so it parts from an ink bar under it; the 11px pixel face, uppercase, 0.05em (`--tracking-state`), `3px 7px`, at most 36ch. It is placed by anchor positioning 6px above its control (the control is the tip's source, so its implicit anchor), hangs from the control's side near a window edge and flips below near the top; inside a vertical strip, or on a control set in vertical writing (a folded rail's toggle), it opens to the right. It opens on a resting pointer after 400ms (at once while another tip has just closed, so sweeping a toolbar reads each name), never on touch, and on keyboard focus (`:focus-visible`) a beat later, only if the control is still on the page and still the keyboard's, so focus a closing dialog or menu hands back does not open a tip mid top-layer change; it can be hovered, it closes on leave and blur (which also cancels a tip still waiting), and using the control (a press, any key) closes it, so a control that renames itself is read afresh next time. Escape closes a tip that focus opened, and nothing else, never the menu or card around it. **The Tip Rule:** a tip shows its element's accessible name and nothing else but the key that does the same, which a key hint shows beside a button's words for the eye alone, which is why it stays out of the accessibility tree; a control that needs a description says it in words beside it. IconButton, the glyph switch, Mark, a chip's dot, the board grip, a menu's glyph toggle and an abbreviated Tag carry one. A pressed toggle keeps its name and lets `aria-pressed` carry the change. The system uses no `title` attribute tooltips.
|
|
1095
1096
|
|
|
1096
1097
|
**Toolbar** (a Mount moves focus, reporting through `toParentMessage`) → `div.toolbar[role="toolbar"][aria-orientation]` (`.floating`): one strip of tools that is one stop for Tab: its own buttons and links, not the items of a menu one of them opens. Arrows on either axis move between tools and stop at the ends, as a cursor's, the palette's and a dialog's action bar's do (a menu and tabs wrap, as their platform patterns do; a vertical strip lies flat along the bottom of a stage on a phone), Home and End go to the ends, and the tool last used is where Tab returns (a roving `tabindex`). The strip is a 2px ink box whose ink shows through a 2px gap between tools, so the rules between them never change with a tool's state. Tools are 28px tall, glyph-only IconButtons 30px wide, labelled Buttons `0 10px` in the 11px pixel face, all paper, head on hover, inverted when `aria-pressed` (the tool in force), muted when disabled, nudged 1px while pressed. A tool's focus ring sits 4px inside it in the tool's own text colour, so it reads on a pressed tool and never lands on the strip's ink. Floating (over a canvas) it takes the 3px button shadow; vertical, its tips open to the right.
|
|
1097
1098
|
|
|
1098
1099
|
### Menus
|
|
1099
1100
|
|
|
1100
|
-
**Menu** (Submodel, pixel-paper's own ARIA menu rather than `@foldkit/ui`'s, whose rows cannot take a role or attributes; it reports `Selected` for an action, a radio item or a link, and `Toggled` for a check item) → `div.menu-wrap > button[aria-haspopup="menu"][aria-expanded]` (the toggle, built on `@foldkit/ui`'s Button) and, while open, `div.menu[role="menu"]`, named by its toggle and hung under it by `@foldkit/ui`'s Anchor, above the page (inside the open dialog when there is one), so no scrolling container clips it. The slip is `position: absolute`, so the `left` and `top` Anchor writes place it (Anchor makes it `fixed` itself under a fixed toggle, in a dialog too). It sits at `z-index: 200`, over the sticky top bar and status bar (40) and a banner (150), under the skip link (300). It hangs 6px under its toggle from its left edge (`"End"`: its right edge) and flips to the other side, or above, when the window has no room; paper, 2px ink border, card shadow, 148px minimum, capped at `100vw - 24px`. Its items are built with `Menu.action`, `Menu.radio`, `Menu.check`, `Menu.link`, `Menu.note` and `Menu.group`. An **action** → `button[role="menuitem"]`; a **link** → `a[role="menuitem"]`, a place to go, which a new tab can open (`aria-current="page"` inverts the page you are on); a **radio** item is one choice among its siblings, `button[role="menuitemradio"][aria-checked]`, inverted when checked. Each holds `span.menu-label`, and an action an optional `keyHint`, the same `kbd.key-hint[aria-hidden="true"]` as Button's (see Keys), at the right in the menu's own face at 11px, no case change, Muted Ink (its item's colour at 0.75 in an inverted item), so the name is the words alone. A **check** item → `button[role="menuitemcheckbox"][aria-checked]` with a 14px `span.menu-check`; a **note** → `span.menu-note`, a muted sans sentence; a **group** heads its items with a note. Items are `7px 12px` between 1px rules, head on hover. An action's or a radio item's `isDisabled` (`[aria-disabled="true"]`) prints it muted with no hover tint and the default cursor; choosing it does nothing, and the arrows still stop on it, as the ARIA menu pattern asks, its ring showing where the keyboard is. Say why beside it in a note. The rows take real focus: the first as the slip opens (the last on ↑ from the toggle); ↑ and ↓ move and wrap, Home and End go to the ends, and a letter goes to the row it starts. Enter, Space or a click chooses and closes it (a check item keeps it open, and the parent flips the value it keeps); Escape or Tab closes it and puts focus back on the toggle; a click elsewhere closes it, and so does the slip leaving the page while open (the row the menu sits on is gone), so the parent's keys never wait on a menu nobody sees. One Menu Model serves one toggle: a menu on every row is a Model per row. `LeftSlip`, `UnmountedSlip` and `CompletedFocus` can arrive after a close and must reach the Model of the menu that sent them, where they change nothing; given to another menu's Model, a late `LeftSlip` or `UnmountedSlip` closes it. The menu does not trap focus, so it hosts no live region.
|
|
1101
|
+
**Menu** (Submodel, pixel-paper's own ARIA menu rather than `@foldkit/ui`'s, whose rows cannot take a role or attributes; it reports `Selected` for an action, a radio item or a link, and `Toggled` for a check item) → `div.menu-wrap > button[aria-haspopup="menu"][aria-expanded]` (the toggle, built on `@foldkit/ui`'s Button) and, while open, `div.menu[role="menu"]`, named by its toggle and hung under it by `@foldkit/ui`'s Anchor, above the page (inside the open dialog when there is one), so no scrolling container clips it. The slip is `position: absolute`, so the `left` and `top` Anchor writes place it (Anchor makes it `fixed` itself under a fixed toggle, in a dialog too). It sits at `z-index: 200`, over the sticky top bar and status bar (40) and a banner (150), under the skip link (300). It hangs 6px under its toggle from its left edge (`"End"`: its right edge) and flips to the other side, or above, when the window has no room; paper, 2px ink border, card shadow, 148px minimum, capped at `100vw - 24px`. Its items are built with `Menu.action`, `Menu.radio`, `Menu.check`, `Menu.link`, `Menu.note` and `Menu.group`. An **action** → `button[role="menuitem"]`; a **link** → `a[role="menuitem"]`, a place to go, which a new tab can open (`aria-current="page"` inverts the page you are on); a **radio** item is one choice among its siblings, `button[role="menuitemradio"][aria-checked]`, inverted when checked. Each holds `span.menu-label`, and an action an optional `keyHint`, the same `kbd.key-hint[aria-hidden="true"]` as Button's (see Keys), at the right in the menu's own face at 11px, no case change, Muted Ink (its item's colour at 0.75, `--fade-on-ink`, in an inverted item), so the name is the words alone. A **check** item → `button[role="menuitemcheckbox"][aria-checked]` with a 14px `span.menu-check`; a **note** → `span.menu-note`, a muted sans sentence; a **group** heads its items with a note. Items are `7px 12px` between 1px rules, head on hover. An action's or a radio item's `isDisabled` (`[aria-disabled="true"]`) prints it muted with no hover tint and the default cursor; choosing it does nothing, and the arrows still stop on it, as the ARIA menu pattern asks, its ring showing where the keyboard is. Say why beside it in a note. The rows take real focus: the first as the slip opens (the last on ↑ from the toggle); ↑ and ↓ move and wrap, Home and End go to the ends, and a letter goes to the row it starts. Enter, Space or a click chooses and closes it (a check item keeps it open, and the parent flips the value it keeps); Escape or Tab closes it and puts focus back on the toggle; a click elsewhere closes it, and so does the slip leaving the page while open (the row the menu sits on is gone), so the parent's keys never wait on a menu nobody sees. One Menu Model serves one toggle: a menu on every row is a Model per row. `LeftSlip`, `UnmountedSlip` and `CompletedFocus` can arrive after a close and must reach the Model of the menu that sent them, where they change nothing; given to another menu's Model, a late `LeftSlip` or `UnmountedSlip` closes it. The menu does not trap focus, so it hosts no live region.
|
|
1101
1102
|
|
|
1102
1103
|
**ContextMenu** (Submodel) → the same slip as `div.menu.at-point[role="menu"]`, opened where a right click lands (at the thing's box for the context menu key or Shift+F10), kept 12px inside the window, with no gap under the pointer; its first item takes focus, and closing it returns focus to where it was unless the reader moved it. One context menu (one Model) serves every thing that spreads `ContextMenu.target` (a row, a card, a table row; give it an id so focus can return), and its OutMessages carry the `targetId`. It is a shortcut, never the only way in: the same items are always in a visible Menu on the thing (a quiet `more` glyph toggle on a row), which is the keyboard's and the phone's way. Never an action found only there.
|
|
1103
1104
|
|
|
1104
1105
|
### Rows and the outline
|
|
1105
1106
|
|
|
1106
|
-
**Rows** → `ul.rows` (`.tree` for an outline); each row, `Rows.rowView` → `li > div.row` (`.context`) with an optional nested `ul.rows`. A context row (outside a filter, printed only to keep a match on its chain) is muted and carries no chips, and since muting is colour alone it ends its text with `span.sr-only` " (context)", which a selectable row's box takes into its name. A 34px flex row (`5px 12px`, 8px gap): an 18px glyph cell (`Rows.glyphView` → `span.glyph`, or `Rows.toggleView` → `button.glyph[aria-expanded]` with the ring glyph open or closed, named "Rows under" its label whether open or not), `div.grow` holding the text (a `label.grow` when it is the label of the row's box; `Rows.titleView` → `button.title`, underlined on hover, `.done-title` when done), `span.slot` for chips, marks and due days (it keeps 58px, so a status that must not cost the title its room goes before it instead, as a `Chip.dotView` in the glyph cell), the row's actions, and `span.tools` (`tools`, icon buttons) resting at 55% opacity until hover or focus-within (hidden on phones). Hover and focus-within tint the row head with `background-color` only, so the indent guides survive; the cursor row takes neither. A keyboard-driven list is a treegrid: `ul.rows[role="treegrid"]` named by its label, each `li` and nested `ul` stepping aside (`role="none"`), each `div.row[role="row"]` carrying `aria-level`, `aria-posinset`, `aria-setsize` and, on a row that folds, `aria-expanded`, and each of its parts a `role="gridcell"` (the box's `span.row-check`, the text, `span.slot`, `span.tools`, and `span.cell` around the glyph and around the actions, laid out as the row lays them out). It puts its cursor on `div.row` (`aria-current="true"`, a roving `tabindex`), which the stylesheet outlines (see "The cursor" below), through `Rows.rowView`'s `attributes`, which go on `div.row` rather than its `li` and also carry the row's name and its key handlers (never a class). A list no keyboard drives stays `ul > li`; a row there named for a screen reader takes `role="group"` with its name. **A row that is its link** (`isLink` → `div.row.linked`): where a row's text is one link (a task in a sidebar, a page), the whole row is that link's target, as a selectable row's text is its box's label, so the reader presses the row, not the words. The link's `::after` covers the row (the row places it; the text's clip, having no position, does not cut it), and only the row's controls (the box, a fold toggle, buttons and fields in the actions and tools) and whatever carries a tip (a dot) sit above it, so a menu stays its own target and a dot's tip still shows, while a chip or a due day in the slot is the link's like the rest of the row. The row takes no Tab stop of its own: the link is its text's one stop, and its controls keep theirs. The link is plain ink with no underline at rest and underlined while the row is hovered; its ring is drawn round the row, 2px ink 4px inside it, not round the words. A link that is `aria-current` prints its row inverted, as a place you are in and a board's cursor card do: a dot's border takes the paper, what prints muted on paper (a glyph, a due day, an age, a tag, a quiet tool) prints in paper at 0.75, lateness takes a red slip, hover neither tints nor underlines it, and a ring inside prints in paper. It is for a list no keyboard drives: a treegrid's row is already its own stop, so `isLink` with `treegrid` is ignored. A folding row's glyph cell holds its toggle, so its dot goes first in its text, before the link, where its tip still lifts it above the link. Nested lists indent 26px on a 1px rule guide. **InlineInput** (Submodel; its `init` focuses and selects the field, so fold it in with `Update.foldChildInit`) → `input.inline-input` swaps in for text at exactly `1lh + 1px` so the row never moves. Enter or leaving the field reports new, non-empty text, trimmed, as `Committed`; an untouched field reports nothing, so a remote edit is never written back. The field then holds `readonly` and `aria-busy` (muted, still focused) until the parent answers with `InlineInput.succeeded` or `InlineInput.failed`: a failure keeps the field and its text for another try and, when it is a refusal with a reason, says why through `InlineInput.errorView`, which the field points at (`aria-invalid`, a red rule, and `aria-describedby`); the parent reads the held reason with `InlineInput.maybeError`. The parent's own `error` (the item's parent was deleted while editing) marks the field the same way, given to the view and to `errorView` alike, and shows before a refused save's reason. `Ended` says the edit is over (`"Enter"`, `"Blur"` or `"Escape"`, and `"Blur"` when focus left while the save was out) with the id the edit's last save returned (`maybeLastSavedId`, the item a repeat added last); the parent moves focus on `"Enter"` and `"Escape"` only, so a clicked destination keeps it. `isRepeating` (adding several) empties the field after each saved Enter instead of ending; Enter on an empty one ends. The text lives in the Submodel's Model, so it outlives a render. Escape is stopped from closing a dialog around it; an IME's Enter and Escape stay with the IME; removing the field writes nothing. **EditableTitle** (Submodel; it reports `Committed`, which the parent answers with `EditableTitle.succeeded` or `failed`) → `button.title-display` ⇄ `input.inline-input.title-edit` at 16px/600; Enter and Escape put focus back on the title.
|
|
1107
|
+
**Rows** → `ul.rows` (`.tree` for an outline); each row, `Rows.rowView` → `li > div.row` (`.context`) with an optional nested `ul.rows`. A context row (outside a filter, printed only to keep a match on its chain) is muted and carries no chips, and since muting is colour alone it ends its text with `span.sr-only` " (context)", which a selectable row's box takes into its name. A 34px flex row (`5px 12px`, 8px gap): an 18px glyph cell (`Rows.glyphView` → `span.glyph`, or `Rows.toggleView` → `button.glyph[aria-expanded]` with the ring glyph open or closed, named "Rows under" its label whether open or not), `div.grow` holding the text (a `label.grow` when it is the label of the row's box; `Rows.titleView` → `button.title`, underlined on hover, `.done-title` when done), `span.slot` for chips, marks and due days (it keeps 58px, so a status that must not cost the title its room goes before it instead, as a `Chip.dotView` in the glyph cell), the row's actions, and `span.tools` (`tools`, icon buttons) resting at 55% opacity until hover or focus-within (hidden on phones). Hover and focus-within tint the row head with `background-color` only, so the indent guides survive; the cursor row takes neither. A keyboard-driven list is a treegrid: `ul.rows[role="treegrid"]` named by its label, each `li` and nested `ul` stepping aside (`role="none"`), each `div.row[role="row"]` carrying `aria-level`, `aria-posinset`, `aria-setsize` and, on a row that folds, `aria-expanded`, and each of its parts a `role="gridcell"` (the box's `span.row-check`, the text, `span.slot`, `span.tools`, and `span.cell` around the glyph and around the actions, laid out as the row lays them out). It puts its cursor on `div.row` (`aria-current="true"`, a roving `tabindex`), which the stylesheet outlines (see "The cursor" below), through `Rows.rowView`'s `attributes`, which go on `div.row` rather than its `li` and also carry the row's name and its key handlers (never a class). A list no keyboard drives stays `ul > li`; a row there named for a screen reader takes `role="group"` with its name. **A row that is its link** (`isLink` → `div.row.linked`): where a row's text is one link (a task in a sidebar, a page), the whole row is that link's target, as a selectable row's text is its box's label, so the reader presses the row, not the words. The link's `::after` covers the row (the row places it; the text's clip, having no position, does not cut it), and only the row's controls (the box, a fold toggle, buttons and fields in the actions and tools) and whatever carries a tip (a dot) sit above it, so a menu stays its own target and a dot's tip still shows, while a chip or a due day in the slot is the link's like the rest of the row. The row takes no Tab stop of its own: the link is its text's one stop, and its controls keep theirs. The link is plain ink with no underline at rest and underlined while the row is hovered; its ring is drawn round the row, 2px ink 4px inside it, not round the words. A link that is `aria-current` prints its row inverted, as a place you are in and a board's cursor card do: a dot's border takes the paper, what prints muted on paper (a glyph, a due day, an age, a tag, a quiet tool) prints in paper at 0.75 (`--fade-on-ink`), lateness takes a red slip, hover neither tints nor underlines it, and a ring inside prints in paper. It is for a list no keyboard drives: a treegrid's row is already its own stop, so `isLink` with `treegrid` is ignored. A folding row's glyph cell holds its toggle, so its dot goes first in its text, before the link, where its tip still lifts it above the link. Nested lists indent 26px on a 1px rule guide. **InlineInput** (Submodel; its `init` focuses and selects the field, so fold it in with `Update.foldChildInit`) → `input.inline-input` swaps in for text at exactly `1lh + 1px` so the row never moves. Enter or leaving the field reports new, non-empty text, trimmed, as `Committed`; an untouched field reports nothing, so a remote edit is never written back. The field then holds `readonly` and `aria-busy` (muted, still focused) until the parent answers with `InlineInput.succeeded` or `InlineInput.failed`: a failure keeps the field and its text for another try and, when it is a refusal with a reason, says why through `InlineInput.errorView`, which the field points at (`aria-invalid`, a red rule, and `aria-describedby`); the parent reads the held reason with `InlineInput.maybeError`. The parent's own `error` (the item's parent was deleted while editing) marks the field the same way, given to the view and to `errorView` alike, and shows before a refused save's reason. `Ended` says the edit is over (`"Enter"`, `"Blur"` or `"Escape"`, and `"Blur"` when focus left while the save was out) with the id the edit's last save returned (`maybeLastSavedId`, the item a repeat added last); the parent moves focus on `"Enter"` and `"Escape"` only, so a clicked destination keeps it. `isRepeating` (adding several) empties the field after each saved Enter instead of ending; Enter on an empty one ends. The text lives in the Submodel's Model, so it outlives a render. Escape is stopped from closing a dialog around it; an IME's Enter and Escape stay with the IME; removing the field writes nothing. **EditableTitle** (Submodel; it reports `Committed`, which the parent answers with `EditableTitle.succeeded` or `failed`) → `button.title-display` ⇄ `input.inline-input.title-edit` at 16px/600; Enter and Escape put focus back on the title.
|
|
1107
1108
|
|
|
1108
1109
|
**The cursor:** `div.row[aria-current="true"]`, set on the row by the application, the one row the keyboard's commands act on. It is drawn, not filled: a 1px Muted Ink outline 4px inside the row (`outline-offset: -4px`), so a nested row's follows its indent and none leaves the panel. It takes no fill at all, not even the hover or focus tint other rows take, and its text, chips and tools keep their own colours (the tools at full opacity). Focus on the row itself thickens the same line to 2px (`--line`): one line inside the row, never a second ring outside it, for any focused row. It is neither focus nor selection (the check cell), it stays put when focus leaves, and moving it is the application's (see Keys). Controls in it keep their own ring: the text cell (`.grow`) clips, but with 4px of room inside the clip, so a title's ring prints whole; an InlineInput in a row shows its focus as its caret and its rule doubled to 2px (drawn inside its box, so the row keeps its height; red while refused), not a ring. A board's cursor card is inverted instead (see Board).
|
|
1109
1110
|
|
|
@@ -1111,11 +1112,11 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
|
|
|
1111
1112
|
|
|
1112
1113
|
### Board
|
|
1113
1114
|
|
|
1114
|
-
**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.
|
|
1115
|
+
**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% (`--fade-dragged`) 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 (`--fade-on-ink`), 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.
|
|
1115
1116
|
|
|
1116
1117
|
### Sortable
|
|
1117
1118
|
|
|
1118
|
-
**Sortable** (Submodel over `@foldkit/ui`'s DragAndDrop, for items the reader puts in order: a list of rows, a grid of tiles, or a grid of the page's panels) → `div.sortable (.grid, .panels)` 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`; `"Grid"` prints tiles that wrap, 180px and up, 12px apart: paper slips in a 2px ink border on the 3px print shadow. `"Panels"` prints each item as the parent's own Panel, up to three across in the deck's 24px gutter and 300px and up, so two across and then one as the column narrows; a row takes its tallest panel's height, each panel's foot sits at its bottom, and the list hands each item its grip (`render.grip`) for the panel's `lead` instead of printing it beside the item. The held copy is the whole panel, tinted head on the 8px card shadow, under ids of its own, and the drop line runs down the middle of the gutter. `isReadOnly`, for a reader who may look but not reorder, prints every grip held and lifts nothing, as holding moves does. Each item starts with a quiet `button.iconbtn` grip (in a panel's title bar, for `"Panels"`) (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. Only the grip lifts an item, under a mouse, a finger or a pen (`cursor: grab` and `touch-action: none` on a movable grip only), so the rest of an item selects its text and a finger on it 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. 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.
|
|
1119
|
+
**Sortable** (Submodel over `@foldkit/ui`'s DragAndDrop, for items the reader puts in order: a list of rows, a grid of tiles, or a grid of the page's panels) → `div.sortable (.grid, .panels)` 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`; `"Grid"` prints tiles that wrap, 180px and up, 12px apart: paper slips in a 2px ink border on the 3px print shadow. `"Panels"` prints each item as the parent's own Panel, up to three across in the deck's 24px gutter and 300px and up, so two across and then one as the column narrows; a row takes its tallest panel's height, each panel's foot sits at its bottom, and the list hands each item its grip (`render.grip`) for the panel's `lead` instead of printing it beside the item. The held copy is the whole panel, tinted head on the 8px card shadow, under ids of its own, and the drop line runs down the middle of the gutter. `isReadOnly`, for a reader who may look but not reorder, prints every grip held and lifts nothing, as holding moves does. Each item starts with a quiet `button.iconbtn` grip (in a panel's title bar, for `"Panels"`) (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. Only the grip lifts an item, under a mouse, a finger or a pen (`cursor: grab` and `touch-action: none` on a movable grip only), so the rest of an item selects its text and a finger on it 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. Only the pointer that pressed moves the item; the browser cancelling it puts the item back. The held item dims to 40% (`--fade-dragged`) 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.
|
|
1119
1120
|
|
|
1120
1121
|
### Reel
|
|
1121
1122
|
|
|
@@ -1127,7 +1128,7 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
|
|
|
1127
1128
|
|
|
1128
1129
|
**The Id Rule.** Everything the reel asks of the layout names panes and columns by id, never by position: a pane into a column at a place among its panes, a pane into a column of its own before a column (or after the last), a column before another, a column's width, its panes' weights by pane id. So a request means what it meant after the layout changed under it, applied at once, later, on a server or after another client's move, and what is held keeps its target the same way. A request naming a pane or column the layout no longer has changes nothing. A column `Reel.apply` makes for a pane pulled out takes the pane's id (`notes`, or `notes/2` where a column has it), so pane and column ids share one namespace.
|
|
1129
1130
|
|
|
1130
|
-
Each pane's grip is a quiet `button.iconbtn[data-reel-grip]` (the grip glyph, "Move <pane>", `aria-roledescription="draggable"`, `aria-pressed`, described by the help). Only a grip lifts, under a mouse, a finger or a pen (`cursor: grab`, `touch-action: none`), so a pane's text selects and a terminal takes the pointer; it carries once the pointer has moved 5px. A held pane dims to 40% in place (`.drag-dim`); its copy, drawn by the reel from its label (the title bar alone, tinted head on the 8px card shadow, `aria-hidden`, `inert`, no ids), follows the pointer, so whatever a pane holds is never built twice. Where it would drop, a 2px dashed ink line prints down the gap it would open (`.drop-here`, a new column there) or across the gutter between the panes it would join (`.drop-before`, `.drop-after`), over the layout, so nothing moves under the pointer; near either edge of the reel the reel scrolls under a carried pane, by time, not frames. On the keyboard, Space or Enter on a grip picks it up and the grip takes the keys while it holds it: ← and → step from a column to the gap beside it and on to the next, ↑ and ↓ move among a column's panes, and Space or Enter drops it. Escape puts it back, taken before a dialog sees it, and so does focus leaving the grip (Tab, a click elsewhere), so a field reached by Tab types as it should. A stop where the drop would change nothing is passed over, but for where it started. Each step is said through the page's live region ("Notes, 2 of 2 in column 2 of 3.", and at a gap "Notes, column 4 of 4.", the new column it would make). A held pane that leaves the layout cancels the move ("Move cancelled."), and one held over a column that leaves aims back where it started, so the arrows go on from there.
|
|
1131
|
+
Each pane's grip is a quiet `button.iconbtn[data-reel-grip]` (the grip glyph, "Move <pane>", `aria-roledescription="draggable"`, `aria-pressed`, described by the help). Only a grip lifts, under a mouse, a finger or a pen (`cursor: grab`, `touch-action: none`), so a pane's text selects and a terminal takes the pointer; it carries once the pointer has moved 5px. Where it would land is read from the page once a frame, at the pointer's latest place (a pointer reports moves faster than the page draws), from the columns' edges and the panes' middles in the column under it alone; a release reads the move not read yet and then where it was let go, so the pane lands where the pointer went. Nothing is cached between frames, since the reel scrolls and its layout changes under a carried pane. A held pane dims to 40% (`--fade-dragged`) in place (`.drag-dim`); its copy, drawn by the reel from its label (the title bar alone, tinted head on the 8px card shadow, `aria-hidden`, `inert`, no ids), follows the pointer, so whatever a pane holds is never built twice. Where it would drop, a 2px dashed ink line prints down the gap it would open (`.drop-here`, a new column there) or across the gutter between the panes it would join (`.drop-before`, `.drop-after`), over the layout, so nothing moves under the pointer; near either edge of the reel the reel scrolls under a carried pane, by time, not frames. On the keyboard, Space or Enter on a grip picks it up and the grip takes the keys while it holds it: ← and → step from a column to the gap beside it and on to the next, ↑ and ↓ move among a column's panes, and Space or Enter drops it. Escape puts it back, taken before a dialog sees it, and so does focus leaving the grip (Tab, a click elsewhere), so a field reached by Tab types as it should. A stop where the drop would change nothing is passed over, but for where it started. Each step is said through the page's live region ("Notes, 2 of 2 in column 2 of 3.", and at a gap "Notes, column 4 of 4.", the new column it would make). A held pane that leaves the layout cancels the move ("Move cancelled."), and one held over a column that leaves aims back where it started, so the arrows go on from there.
|
|
1131
1132
|
|
|
1132
1133
|
The splitters size by pointer or by the side arrows (a width) and up and down (a height), 5% of the reel or the column a press, unmodified only: a modified arrow is the application's. A splitter moves a share of the reel's width, or of its column's height, from the sizes the layout holds, never the sizes printed, which the stylesheet may hold at their least: a width goes no narrower than a tenth, and a pane keeps a twentieth of its column, and the stylesheet keeps both at their least on screen. A height splitter sizes the pane above it, named by id, against whichever pane is below it in the layout when the drag moves, so a reorder while it is dragged never resizes a pane in its place; it lets go when the pane above it has no pane below. While a splitter is dragged the reel prints the new sizes as a preview and asks for the size only when let go; the browser cancelling the pointer, or Escape, puts it back.
|
|
1133
1134
|
|
|
@@ -1228,7 +1229,7 @@ Installing: see README.md beside this file. The package is a Foldkit library. It
|
|
|
1228
1229
|
|
|
1229
1230
|
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.
|
|
1230
1231
|
|
|
1231
|
-
Without Foldkit: link `pixel-paper.css` and write the markup named in each component above. The stylesheet relies on these attributes: `aria-current` (`="page"` on a nav segment, a crumb, a menu link, a place's button and a linked row's link; `="true"` on `.row` and `.board-card` for the cursor, given `tabindex="0"` while the others take `-1`, on the open file and on a history's current entry), `aria-selected`, `aria-pressed`, `aria-checked`, `aria-expanded`, `aria-disabled` (on buttons, on a board card's `.title`, on menu items, on a select-all box), `aria-invalid`, `disabled` and `aria-busy` on the Fields `fieldset` while a save is in flight, `aria-hidden="true"` on a button's or menu item's `kbd.key-hint`, `data-label` on stacked-table cells, `data-drag-over` on a drop zone while a file hovers, `data-pixel-paper-dragging` on the root while a sortable's item or a reel's pane is pressed or carried, `--reel-width`, `--reel-weight` and `--reel-anchor` on a reel's columns, slots and panes, `--
|
|
1232
|
+
Without Foldkit: link `pixel-paper.css` and write the markup named in each component above. The stylesheet relies on these attributes: `aria-current` (`="page"` on a nav segment, a crumb, a menu link, a place's button and a linked row's link; `="true"` on `.row` and `.board-card` for the cursor, given `tabindex="0"` while the others take `-1`, on the open file and on a history's current entry), `aria-selected`, `aria-pressed`, `aria-checked`, `aria-expanded`, `aria-disabled` (on buttons, on a board card's `.title`, on menu items, on a select-all box), `aria-invalid`, `disabled` and `aria-busy` on the Fields `fieldset` while a save is in flight, `aria-hidden="true"` on a button's or menu item's `kbd.key-hint`, `data-label` on stacked-table cells, `data-drag-over` on a drop zone while a file hovers, `data-pixel-paper-dragging` on the root while a sortable's item or a reel's pane is pressed or carried, `--reel-width`, `--reel-weight` and `--reel-anchor` on a reel's columns, slots and panes, `--railed-width` on `.railed` for a width the reader gave the rail and `.folded` on it while the reader has hidden the rail, `data-reel-focus` on the control in a pane focus lands on, `data-tip` on glyph-only controls (the words the page's one tip shows), native `<dialog>` for overlays and native `[popover]` for the rail's drawer and tips. Behaviour the components own (board moves, a reel's moves, sizes and focus, a menu's placement and arrow keys, a context menu's placement and focus, tips, a toolbar's roving focus, the palette cursor, tab arrows, focus into dialogs, an error summary's focus, the flash, the remembered palette, the toast stack's hold and focus, a player's controls, the key help) is the adopter's to write.
|
|
1232
1233
|
|
|
1233
1234
|
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.
|
|
1234
1235
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid.d.ts","sourceRoot":"","sources":["../../../src/adapters/mermaid.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mermaid.d.ts","sourceRoot":"","sources":["../../../src/adapters/mermaid.ts"],"names":[],"mappings":"AAcA,OAAO,EAA+B,MAAM,EAAa,MAAM,QAAQ,CAAC;AACxE,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,KAAK,KAAK,MAAM,eAAe,CAAC;AAGvC,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAgO9C;;;;;6DAK6D;AAC7D,eAAO,MAAM,OAAO;;QACM,OAAO;;;QACV,OAAO;QAAW,MAAM;;;QACxB,OAAO;QAAW,MAAM;;EAC7C,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAC;AAI1C;;;uFAGuF;AACvF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;EAkBtB,CAAC;AAIH,yDAAyD;AACzD,MAAM,MAAM,UAAU,CAAC,aAAa,IAAI,QAAQ,CAAC;IAC/C;mBACe;IACf,MAAM,EAAE,MAAM,CAAC;IACf;4EACwE;IACxE,OAAO,EAAE,OAAO,CAAC;IACjB;+BAC2B;IAC3B,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,aAAa,CAAC;CACtD,CAAC,CAAC;AAEH;mFACmF;AACnF,eAAO,MAAM,IAAI,GAAI,aAAa,UACxB,UAAU,CAAC,aAAa,CAAC,KAC9B,WAAW,CAAC,aAAa,CAAC,KAC5B,IAOF,CAAC"}
|
|
@@ -3,30 +3,6 @@ import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
|
|
|
3
3
|
// faces, in the page's palette. An optional entry point ("./adapters/mermaid"):
|
|
4
4
|
// mermaid is the adopter's own dependency, loaded here only when a diagram is drawn.
|
|
5
5
|
//
|
|
6
|
-
// `view` renders the diagram's box and draws into it from a Mount (`DrawDiagram`), so it
|
|
7
|
-
// needs the DOM only once the box is on the page, never while rendering on the server. The
|
|
8
|
-
// box is keyed by the palette the page prints in, from the application's SchemeSwitch
|
|
9
|
-
// (`Scheme.SchemeSwitch.palette`, which under "System" follows the reader's system as it
|
|
10
|
-
// changes), so a new palette mounts a new box and draws the diagram again; a new source does
|
|
11
|
-
// the same. The box reports `SucceededDrawDiagram`, `FailedLoadMermaid` or `FailedDrawDiagram`
|
|
12
|
-
// through the caller's `toParentMessage`, and the caller keeps what it shows meanwhile (a
|
|
13
|
-
// Loading line, an error Notice) in its own Model:
|
|
14
|
-
//
|
|
15
|
-
// Mermaid.view(
|
|
16
|
-
// {
|
|
17
|
-
// source,
|
|
18
|
-
// palette: Scheme.SchemeSwitch.palette(model.schemeSwitch),
|
|
19
|
-
// toParentMessage: (message) => GotDiagramMessage({ message }),
|
|
20
|
-
// },
|
|
21
|
-
// h,
|
|
22
|
-
// );
|
|
23
|
-
//
|
|
24
|
-
// `renderDiagram` waits for the faces, so words are measured in them and not a fallback;
|
|
25
|
-
// renders in the box (it must be on the page), so labels are measured where they will print;
|
|
26
|
-
// and returns the SVG. Mermaid's settings are global, so draws run one at a time. It fails with
|
|
27
|
-
// `MermaidMissing` when mermaid itself does not load, and with `DiagramError` when mermaid
|
|
28
|
-
// loads but the diagram is not drawn.
|
|
29
|
-
//
|
|
30
6
|
// Two layers. themeCSS repaints with var(…), so the parts it names (nodes, edges, labels,
|
|
31
7
|
// clusters, actors, messages, notes) follow a palette switch in place. themeVariables must be
|
|
32
8
|
// colours, not var() strings (mermaid mixes its own shades from them), so they are read from
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAsCL,0EAA0E;AAC1E,eAAO,MAAM,KAAK,aAAc,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CASvD,CAAC;AAIL;;;;GAIG;AACH,eAAO,MAAM,QAAQ;;;;iBAInB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,KAAK;;;;iBAIhB,CAAC"}
|
|
@@ -27,7 +27,7 @@ export const view = (h) => __foldkitBrandViewResult((h.div([
|
|
|
27
27
|
h.Class("sr-only"),
|
|
28
28
|
h.Role("status"),
|
|
29
29
|
h.AriaLive("polite"),
|
|
30
|
-
h.
|
|
30
|
+
h.Attribute(ANNOUNCER_ATTRIBUTE, ""),
|
|
31
31
|
])), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#view");
|
|
32
32
|
// ANNOUNCE
|
|
33
33
|
/** How long words said together are gathered before the region reads them as one. */
|
|
@@ -70,16 +70,15 @@ export const announce = (text) => __foldkitBrandViewResult((Effect.gen(function*
|
|
|
70
70
|
})), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#announce");
|
|
71
71
|
// FLASH
|
|
72
72
|
const FLASH_ID = "pixel-paper-flash";
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const beatOf = (element) =>
|
|
73
|
+
/** The beat in milliseconds, from the stylesheet's `--motion-flash`: none on a page that has
|
|
74
|
+
* not loaded the stylesheet, which has nothing to flash with. */
|
|
75
|
+
const beatOf = (element) => {
|
|
76
|
+
const time = getComputedStyle(element).getPropertyValue("--motion-flash").trim();
|
|
77
|
+
return __foldkitBrandViewResult((pipe(Option.liftPredicate(Number.parseFloat(time), Number.isFinite), Option.map((value) => (__foldkitBrandViewResult((time.endsWith("ms") ? value : value * 1000), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#anonymous~8"))))), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#beatOf");
|
|
78
|
+
};
|
|
76
79
|
const prefersReducedMotion = () => __foldkitBrandViewResult((window.matchMedia("(prefers-reduced-motion: reduce)").matches), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#prefersReducedMotion");
|
|
77
|
-
const runFlash = (element) => {
|
|
78
|
-
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const beat = beatOf(element);
|
|
82
|
-
Array.forEach(Array.filter(element.getAnimations(), (animation) => __foldkitBrandViewResult((animation.id === FLASH_ID), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#anonymous~10")), (animation) => __foldkitBrandViewResult((animation.cancel()), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#anonymous~11"));
|
|
80
|
+
const runFlash = (element, beat) => {
|
|
81
|
+
Array.forEach(Array.filter(element.getAnimations(), (animation) => __foldkitBrandViewResult((animation.id === FLASH_ID), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#anonymous~9")), (animation) => __foldkitBrandViewResult((animation.cancel()), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#anonymous~10"));
|
|
83
82
|
if (prefersReducedMotion()) {
|
|
84
83
|
const ring = "var(--line) solid var(--ink)";
|
|
85
84
|
element.animate({ outline: [ring, ring], outlineOffset: ["-2px", "-2px"] }, { duration: beat, id: FLASH_ID });
|
|
@@ -91,8 +90,9 @@ const runFlash = (element) => {
|
|
|
91
90
|
/** Inverts the element `selector` finds for one beat; `Flash` runs it. */
|
|
92
91
|
export const flash = (selector) => __foldkitBrandViewResult((Effect.sync(() => {
|
|
93
92
|
const maybeElement = Option.fromNullishOr(document.querySelector(selector));
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
const maybeFlash = Option.flatMap(maybeElement, (element) => __foldkitBrandViewResult((Option.map(beatOf(element), (beat) => (__foldkitBrandViewResult(({ element, beat }), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#anonymous~13")))), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#anonymous~12"));
|
|
94
|
+
if (Option.isSome(maybeFlash)) {
|
|
95
|
+
runFlash(maybeFlash.value.element, maybeFlash.value.beat);
|
|
96
96
|
}
|
|
97
97
|
})), "@gjermundgaraba-designs/pixel-paper/src/announcer/announcer.js#flash");
|
|
98
98
|
// COMMAND
|
|
@@ -201,10 +201,6 @@ export declare const init: (config: InitConfig) => Model;
|
|
|
201
201
|
export declare const id: (model: Model) => string;
|
|
202
202
|
/** Whether a card is held by a pointer or the keyboard. */
|
|
203
203
|
export declare const isDragging: (model: Model) => boolean;
|
|
204
|
-
/** Whether a card is pressed or held. While it is, the arrow keys, Space and Escape belong to
|
|
205
|
-
* the board: pause the page's own bindings for them. The board takes Escape before anything
|
|
206
|
-
* else sees it. */
|
|
207
|
-
export declare const isHolding: (model: Model) => boolean;
|
|
208
204
|
/** Says a step of a move through the page's live region (as `Announcer.Announce` does), which is
|
|
209
205
|
* inside the topmost open dialog when there is one. */
|
|
210
206
|
export declare const AnnounceMove: Command.CommandDefinitionWithArgs<"AnnounceMove", {
|
|
@@ -995,9 +991,6 @@ export type ViewInputs = Readonly<{
|
|
|
995
991
|
toEmptyView?: (column: Column) => ReadonlyArray<Html>;
|
|
996
992
|
/** Controls at the right of a column's head, after its count ("capture here"). */
|
|
997
993
|
toColumnActions?: (column: Column) => ReadonlyArray<Html>;
|
|
998
|
-
/** Further attributes for a column's `div.board-col` (`data-status`), never a class. Wrap
|
|
999
|
-
* them with `childAttributes` so their handlers dispatch the parent's Messages. */
|
|
1000
|
-
toColumnAttributes?: (column: Column) => ReadonlyArray<ChildAttribute>;
|
|
1001
994
|
}>;
|
|
1002
995
|
/** Configuration for rendering one card with `cardView`. */
|
|
1003
996
|
export type CardViewConfig<ParentMessage> = Readonly<{
|
|
@@ -1188,9 +1181,6 @@ export declare const view: import("foldkit/submodel").View<{
|
|
|
1188
1181
|
toEmptyView?: (column: Column) => ReadonlyArray<Html>;
|
|
1189
1182
|
/** Controls at the right of a column's head, after its count ("capture here"). */
|
|
1190
1183
|
toColumnActions?: (column: Column) => ReadonlyArray<Html>;
|
|
1191
|
-
/** Further attributes for a column's `div.board-col` (`data-status`), never a class. Wrap
|
|
1192
|
-
* them with `childAttributes` so their handlers dispatch the parent's Messages. */
|
|
1193
|
-
toColumnAttributes?: (column: Column) => ReadonlyArray<ChildAttribute>;
|
|
1194
1184
|
}>>;
|
|
1195
1185
|
export {};
|
|
1196
1186
|
//# sourceMappingURL=board.d.ts.map
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../../src/board/board.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,MAAM,EAAgB,MAAM,EAAE,MAAM,EAAQ,MAAM,QAAQ,CAAC;AAC3E,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,IAAI,EACT,KAAK,WAAW,EAEjB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,KAAK,MAAM,eAAe,CAAC;AAGvC,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,MAAM,MAAM,gBAAgB,CAAC;AAIzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAsB9D;uDACuD;AACvD,eAAO,MAAM,IAAI;;;EAA6D,CAAC;AAC/E,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,CAAC;AAEpC,wEAAwE;AACxE,eAAO,MAAM,MAAM;;;;;;;EAIjB,CAAC;AACH,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAC;AAyCxC;4FAC4F;AAC5F,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAIhB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAC;AAQtC,iDAAiD;AACjD,eAAO,MAAM,OAAO;;QACO,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAE9B,MAAM;QACN,IAAI;QACJ,WAAW;QACX,OAAO;QACP,OAAO;QACP,OAAO;QACP,OAAO;QACP,OAAO;;;;;;;;;;QAES,MAAM;QAAiB,OAAO;;;;;;;;;;;;EAIhD,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAC;AAI1C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU;;QAEnB,MAAM;QACN,YAAY;QACZ,SAAS;QACT,UAAU;QACV,OAAO;QACP,gBAAgB;QAChB,iBAAiB;;;QAEH,MAAM;;EACtB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAC;AAIhD;+FAC+F;AAC/F,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,EAAE,EAAE,MAAM,CAAC;IACX;qFACiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC,CAAC;AAEH;iCACiC;AACjC,eAAO,MAAM,IAAI,WAAY,UAAU,KAAG,KAIxC,CAAC;AAIH,8CAA8C;AAC9C,eAAO,MAAM,EAAE,UAAW,KAAK,KAAG,MAA8B,CAAC;AAEjE,2DAA2D;AAC3D,eAAO,MAAM,UAAU,UAAW,KAAK,KAAG,OAAsD,CAAC;AAgBjG;wDACwD;AACxD,eAAO,MAAM,YAAY;;;;iBAIvB,CAAC;AAIH,KAAK,YAAY,GAAG,MAAM,CAAC,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AA6R5E,qFAAqF;AACrF,eAAO,MAAM,MAAM,UAAW,KAAK,WAAW,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBjD,CAAC;AAIL;;;GAGG;AACH,eAAO,MAAM,MAAM,UAAW,KAAK,KAAG,YAAkD,CAAC;AAEzF;;;GAGG;AACH,eAAO,MAAM,UAAU,UAAW,KAAK,aAAa,OAAO,KAAG,YAG7D,CAAC;AAoBF;;;;;;gDAMgD;AAChD,eAAO,MAAM,aAAa,GAAI,MAAM,SAAS,MAAM,aAAa,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mGAC3B,CAAC;AAI5C;0EAC0E;AAC1E,eAAO,MAAM,cAAc;;;;;;EAQzB,CAAC;AAIH,0FAA0F;AAC1F,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,kFAAkF;IAClF,UAAU,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IAC1C,mEAAmE;IACnE,SAAS,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,MAAM,EAAE,OAAO,CAAC;IAChB;4DACwD;IACxD,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAC1B;2CACuC;IACvC,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC,CAAC;AAEH;;;sCAGsC;AACtC,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B;kCAC8B;IAC9B,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IACrD,mEAAmE;IACnE,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,CAAC,IAAI,CAAC,CAAC;IACtD,kFAAkF;IAClF,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,CAAC,IAAI,CAAC,CAAC;CAC3D,CAAC,CAAC;AA8HH,4DAA4D;AAC5D,MAAM,MAAM,cAAc,CAAC,aAAa,IAAI,QAAQ,CAAC;IACnD,0CAA0C;IAC1C,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;wBACoB;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;4DACwD;IACxD,MAAM,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACtC;+EAC2E;IAC3E,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACpC;8BAC0B;IAC1B,UAAU,CAAC,EAAE,UAAU,CAAC,aAAa,CAAC,CAAC;CACxC,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,GAAI,aAAa,UAC5B,cAAc,CAAC,aAAa,CAAC,KAClC,WAAW,CAAC,aAAa,CAAC,KAC5B,IA4BF,CAAC;AAgHF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAhUN,aAAa,CAAC,MAAM,CAAC;IAC9B;kCAC8B;gBAClB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI;IACpD,mEAAmE;kBACrD,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,CAAC,IAAI,CAAC;IACrD,kFAAkF;sBAChE,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,CAAC,IAAI,CAAC;GAuUzD,CAAC"}
|