@gjermundgaraba-designs/pixel-paper 0.11.0 → 0.13.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 +13 -13
- package/dist/src/emptyState/emptyState.d.ts +4 -0
- package/dist/src/emptyState/emptyState.d.ts.map +1 -1
- package/dist/src/emptyState/emptyState.js +4 -2
- package/dist/src/field/field.d.ts +14 -6
- package/dist/src/field/field.d.ts.map +1 -1
- package/dist/src/field/field.js +17 -5
- package/dist/src/fieldset/fieldset.d.ts +7 -2
- package/dist/src/fieldset/fieldset.d.ts.map +1 -1
- package/dist/src/fieldset/fieldset.js +10 -4
- package/dist/src/lineDiff/diff.d.ts +26 -31
- package/dist/src/lineDiff/diff.d.ts.map +1 -1
- package/dist/src/lineDiff/diff.js +62 -41
- package/dist/src/lineDiff/index.d.ts +2 -2
- package/dist/src/lineDiff/index.d.ts.map +1 -1
- package/dist/src/lineDiff/index.js +1 -1
- package/dist/src/lineDiff/lineDiff.d.ts +14 -0
- package/dist/src/lineDiff/lineDiff.d.ts.map +1 -1
- package/dist/src/lineDiff/lineDiff.js +43 -21
- package/dist/src/passwordField/passwordField.d.ts +11 -6
- package/dist/src/passwordField/passwordField.d.ts.map +1 -1
- package/dist/src/passwordField/passwordField.js +10 -7
- package/dist/src/places/places.d.ts +15 -5
- package/dist/src/places/places.d.ts.map +1 -1
- package/dist/src/places/places.js +19 -17
- package/dist/src/radioChoices/radioChoices.d.ts +4 -4
- package/dist/src/radioChoices/radioChoices.d.ts.map +1 -1
- package/dist/src/rail/rail.d.ts +2 -2
- package/dist/src/rail/rail.d.ts.map +1 -1
- package/dist/src/rail/rail.js +5 -3
- package/dist/src/rows/index.d.ts +1 -1
- package/dist/src/rows/index.d.ts.map +1 -1
- package/dist/src/rows/rows.d.ts +31 -5
- package/dist/src/rows/rows.d.ts.map +1 -1
- package/dist/src/rows/rows.js +46 -17
- package/dist/src/selectField/selectField.d.ts +7 -5
- package/dist/src/selectField/selectField.d.ts.map +1 -1
- package/dist/src/selectField/selectField.js +12 -6
- package/dist/src/sheet/sheet.d.ts +3 -2
- package/dist/src/sheet/sheet.d.ts.map +1 -1
- package/dist/src/sheet/sheet.js +3 -2
- package/dist/src/slot/slot.d.ts +2 -1
- package/dist/src/slot/slot.d.ts.map +1 -1
- package/dist/src/slot/slot.js +2 -2
- package/dist/src/textareaField/textareaField.d.ts +15 -5
- package/dist/src/textareaField/textareaField.d.ts.map +1 -1
- package/dist/src/textareaField/textareaField.js +15 -6
- package/package.json +1 -1
- package/src/emptyState/emptyState.ts +8 -2
- package/src/field/field.ts +37 -8
- package/src/fieldset/fieldset.ts +16 -4
- package/src/lineDiff/diff.ts +104 -48
- package/src/lineDiff/index.ts +2 -2
- package/src/lineDiff/lineDiff.ts +76 -31
- package/src/passwordField/passwordField.ts +18 -9
- package/src/pixel-paper.css +41 -6
- package/src/places/places.ts +43 -30
- package/src/radioChoices/radioChoices.ts +2 -2
- package/src/rail/rail.ts +5 -3
- package/src/rows/index.ts +1 -0
- package/src/rows/rows.ts +95 -25
- package/src/selectField/selectField.ts +17 -17
- package/src/sheet/sheet.ts +3 -2
- package/src/slot/slot.ts +4 -1
- package/src/textareaField/textareaField.ts +39 -9
package/DESIGN.md
CHANGED
|
@@ -804,7 +804,7 @@ Two neutral palettes over one set of tokens, with three meaning hues shared betw
|
|
|
804
804
|
|
|
805
805
|
- **Marker Amber** (`{colors.amber}`, both palettes): the Mark (a viewfinder glyph on an amber square beside a marked item), the warn chip (an update waiting, a source behind), a meter reading at or above 80% of its limit, the glyph box of a warning notice, of a confirm dialog and of a reconnecting banner; a stat's change or a bar's reading that the product calls a warning. Always carries Printer Ink text (`{colors.on-status}`); an amber glyph on paper is never allowed because it fails contrast. Where every row is marked the Mark prints plain (muted, no fill): amber is for the one among many.
|
|
806
806
|
- **Active Green** (`{colors.green}`, both palettes): the go chip of something that is live or in sync. Also carries Printer Ink text. Nothing else is green.
|
|
807
|
-
- **Urgent Red** (`{colors.red}` paper / `{colors.red-plum}` plum): the danger button's text, border and shadow tint; the blocked and limit-reached chips; the error notice's and error toast's border and glyph box; the offline word and an offline banner's glyph box; a stat's change or a bar's reading at fault; a broken image, video or audio frame; a stale age, a late due day (on a cursor row or card, a red slip with `on-red` text), a failed check, a stale meter's foot; a failed field's rule and label, a failed radio or select box, a fieldset's legend over a failed control; a link to a page that does not exist; the minus sign of a deleted diff line. Red fills carry Paper White text on paper (`{colors.on-red}`) and Printer Ink on plum (`{colors.on-red-plum}`), because plum lightens red for text contrast.
|
|
807
|
+
- **Urgent Red** (`{colors.red}` paper / `{colors.red-plum}` plum): the danger button's text, border and shadow tint; the blocked and limit-reached chips; a field's error line; the error notice's and error toast's border and glyph box; the offline word and an offline banner's glyph box; a stat's change or a bar's reading at fault; a broken image, video or audio frame; a stale age, a late due day (on a cursor row or card, a red slip with `on-red` text), a failed check, a stale meter's foot; a failed field's rule and label, a failed radio or select box, a fieldset's legend over a failed control; a link to a page that does not exist; the minus sign of a deleted diff line. Red fills carry Paper White text on paper (`{colors.on-red}`) and Printer Ink on plum (`{colors.on-red-plum}`), because plum lightens red for text contrast.
|
|
808
808
|
|
|
809
809
|
### Neutral
|
|
810
810
|
|
|
@@ -929,7 +929,7 @@ Depth is printed, not lit. Every raised surface casts a solid rectangle of Shado
|
|
|
929
929
|
### Shadow Vocabulary
|
|
930
930
|
|
|
931
931
|
- **Card** (`box-shadow: var(--shadow-card)`, 8px): dialog cards and menus; the highest print.
|
|
932
|
-
- **Panel** (`box-shadow: var(--shadow-panel)`, 5px): panels and the open select picker.
|
|
932
|
+
- **Panel** (`box-shadow: var(--shadow-panel)`, 5px): panels, sheets and the open select picker.
|
|
933
933
|
- **Button** (`box-shadow: var(--shadow-button)`, 3px): buttons, the filter strip, board cards, the Banner, toasts, the wall's corner slip.
|
|
934
934
|
- **Small button** (`box-shadow: var(--shadow-button-small)`, 2px): small buttons.
|
|
935
935
|
- **Pressed** (`box-shadow: var(--shadow-pressed)`, 1px, with `translate(2px, 2px)`): the active state of any button.
|
|
@@ -1009,7 +1009,7 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
|
|
|
1009
1009
|
- **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).
|
|
1010
1010
|
- **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.
|
|
1011
1011
|
- **Crumbs** → `nav.crumbs > ol > li`: the path in the 11px pixel face, muted slashes between, the last crumb ink with `aria-current="page"`, the others muted with a dotted underline on hover.
|
|
1012
|
-
- **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 > button` 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.
|
|
1012
|
+
- **Places** (a View over `@foldkit/ui`'s Button) → `nav.places[aria-label] > ul > li`, each place (keyed by an `id` that is stable and unique among the places, so two places that share a name stay apart) → `li > a[href]` when the place has an address (an area, a saved view: it opens in a new tab and reads as a link) or `li > button` when choosing it only changes the page, holding `span.place-name` and `span.place-count`, then `span.place-tools`: a column of places to go (areas, collections, saved filters), flush in a rail panel. Each line is at least 34px (`--row`), `8px 12px`, over a 1px rule; the name in the sans, sentence case, wrapping anywhere rather than clipping; the count in the 11px pixel face, muted, at the right; a quiet IconButton after it acts on the place itself (remove a saved filter). Hover tints head. The place you are in carries `aria-current="page"` and its whole `li` prints inverted, the count and a quiet tool in paper at 0.75, never tinted by hover; the ring sits 4px inside the line, in paper on the current one.
|
|
1013
1013
|
- **PageHead** → `header.page-head > div.page-name > h1.page-title + span.meta`, then `div.page-acts`: the heading of a working screen, one per screen, above its panels (which title in h2). The name in the page-title register with a count or state beside it in the 11px pixel face; the screen's own Buttons at the right, 8px apart, wrapping under the name when the line runs out (`14px × 24px` gaps), aligned to the name's foot. No surface of its own: it stands on the ground. There is no label above the heading; the name says where you are.
|
|
1014
1014
|
- **TopBar** (a Mount measures it, reporting through `toParentMessage`) → `header.topbar` (brand, nav, `div.topbar-tools`), **Wordmark** → `.wordmark` with `span.cursor`, **LinkState** → `span.link-state[role="status"]`: online prints plain; `.connecting` (the first reach) prints muted with the working cursor; `.reconnecting` (the link dropped and is coming back) fills amber with `on-status` and the cursor; `.offline` (given up) fills red with `on-red`. Reconnecting and offline stay in a phone's top bar; the other two hide there.
|
|
1015
1015
|
- **StatusBar** (a Mount measures it, reporting through `toParentMessage`) → `footer.statusbar > div.statusbar-state + p.statusbar-keys`: the quiet line along the bottom of an interface, the page's footer (one per page, after the rail and the deck, outside `main`). Fixed to the bottom edge on paper over a 1px rule, in the pixel face at the state size in Muted Ink: the status at the left (a LinkState, a sync word), the keys at the right, each a `kbd.key-hint` in ink (printed as the help prints it, ⌘K or Ctrl+K by platform) and its word, plain text and not pressable. Narrow, the keys wrap under the status; nothing hides. It keeps `--statusbar-h` on the page at its height (25px, one line, until it is measured), like the top bar's `--topbar-h`: the page pads its end by it, a scrolled-to anchor stays clear of it, and the rail, the stage, a Banner and the toast stack all stop above it.
|
|
@@ -1039,10 +1039,10 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
|
|
|
1039
1039
|
|
|
1040
1040
|
- **Panel** → `section.panel[aria-labelledby] > header.panel-tb > h2 + span.meta`, then `div.panel-body` (or the body directly when `isFlush`), then `div.panel-foot`. The section is keyed by its `id`, so a panel among siblings that come and go keeps its own element, and a panel under another id is another element: one that replaces another in the same place is built anew (a live region inside it is inserted, not kept), so keep one panel under one id across states that should share it. Square, 2px ink border, Paper White, panel shadow. The title bar is inverted Departure Mono 13px uppercase 0.08em (`--tracking-title`), `padding: 6px 12px`; its meta is 11px at 75% opacity. A control in a title bar (a panel's, a window's or a card's) rings in paper, 2px inside its own edge, because the ink ring would vanish on the ink bar. The body pads 16px with a 14px grid gap; the foot pads `10px 12px` over a 1px rule, with a `.push` slot that goes right. `actions` puts small Buttons and IconButtons in `span.panel-acts` (6px apart) at the title bar's right end, after the meta; the bar trims to `3px 3px 3px 12px` at 31px or more. On the ink bar a button is a paper outline (a primary one fills paper), flat, nudged 1px when pressed, its ring 4px inside in its own colour; disabled, it prints at 60%.
|
|
1041
1041
|
- **Deck** → `main.deck#main`: the column of panels.
|
|
1042
|
-
- **EmptyState** → `div.empty > strong + p
|
|
1042
|
+
- **EmptyState** → `div.empty > strong.empty-title + p` (an `h1` to `h6.empty-title` with `headingLevel`, where the empty state is a page's or a section's main content, looking the same): centred, `26px 16px`, a 12px uppercase 0.08em (`--tracking-title`) headline, a muted 46ch sentence, an optional action 6px below.
|
|
1043
1043
|
- **Wall** → `main.wall`, `Wall.markView` → `div.wall-mark` (a 30px ink-bordered glyph box beside the 22px wordmark). `Wall.cornerView` → `aside.wall-corner[aria-label]`, rendered just before the Wall: a paper slip in a 2px ink box on the 3px shadow, `6px 8px`, holding what a top bar would (see Layout).
|
|
1044
1044
|
- **Banner** → `div.banner > span.glyph (.error, .ok, .quiet) + span`: a fixed paper slip on the 3px shadow with a 26px glyph box in the Notice's tones and a sentence that says what happens to the reader's work. It holds a state the whole interface is in, for as long as it lasts, one at a time. It is not a live region (it arrives already holding its sentence, which would go unheard): the application says the state once, when it begins, with `Announcer.announce` from its own Command. `"Error"` (the default; red, bang): a state that stops work (offline). `"Warn"` (amber): a state that is passing (reconnecting). `"Ok"` (ink, check): the state just ended (back online), shown briefly and then taken away. `"Quiet"` (head, info): news. With the connection word it is one pair: LinkState `"Reconnecting"` beside a warn Banner, `"Offline"` beside an error Banner with a retry, then `"Online"` beside an ok Banner for a moment; `"Connecting"` takes none.
|
|
1045
|
-
- **Rail** (Submodel) → `div.railed > aside.rail[popover="auto"][aria-label]
|
|
1045
|
+
- **Rail** (Submodel) → `div.railed > main.deck#main + aside.rail[popover="auto"][aria-label]`: the rail's panels (a tree, places, filters) beside the deck's, after it in the markup so the page's main content and its h1 come first in reading order; the stylesheet fixes the rail at the left under the top bar. One element serves both layouts, so nothing in it renders twice or loses its state: at rest it stands there (its `popover` attribute is inert until opened), and at 900px and under it is hidden and `span.rail-open > button.btn.small[aria-expanded]` at the top of the deck opens it as a drawer: a slice of the dithered ground, `min(320px, 100vw - 40px)` wide from the left edge, closed by a 2px ink rule and the 8px card shadow, entering from the left in three steps over a veil. Its head (`div.rail-head`: the name in the label register and a close IconButton) shows only in the drawer. Opening focuses its first control; Escape, a click on the veil, the close glyph or focus leaving closes it, and all but the last return focus to the button. Widening the window past 900px closes it. The parent reads whether the drawer is open with `Rail.isOpen`, to hold its own page keys while the drawer covers the page. A file tree flush in a rail panel (or any panel) drops its own border.
|
|
1046
1046
|
- **Stage** → `main.stage#main > div.stage-work > div.stage-canvas + div.stage-tools + div.stage-bar`: see Layout. `paused` (a Notice saying why: the link dropped, the page is read-only) veils the work when editing stops: `div.stage-paused[role="status"]` lays the dialog veil and its ink grain over everything and centres that Notice on the 3px button shadow, while the work beneath is `inert`. It lifts when `paused` is not given; the connection word says reconnecting meanwhile.
|
|
1047
1047
|
- **Window** → `section.panel.window (.flush) > header.panel-tb + div.window-body + div.panel-foot`: a Panel for a stage, as tall and wide as whatever places it (and moved by it, if anything moves it). Its title bar is the panel's, trimmed to hold tools: 30px tall (`3px 3px 3px 10px`), centred, with an optional kind glyph, the title (clipped with an ellipsis), a status chip (its border turns paper so it reads on ink), tools and a close IconButton, both in the bar's paper. The body scrolls inside (`16px`, a 14px grid); flush, what fills it (a code surface, a file tree, a diff) drops its border and fills it. A foot is a panel foot; a notice there takes the whole line.
|
|
1048
1048
|
- **Loading** → `div.loading[role="status"]`: where content is on its way, centred like an EmptyState at `26px 16px`, a 12px uppercase 0.08em (`--tracking-title`) muted line ("loading the items") ending in the 6×11px working cursor. Its status element stays in the page and only its words change: with `isLoading` false it is `div.loading-idle[role="status"]`, empty and out of the flow (`position: absolute`, never `display: none`, which would drop the live region), so it takes no room and no gap at the top of a panel's or a card's body, and a screen reader hears the line when it arrives. The content or an EmptyState takes its place on screen; the line never prints beside either.
|
|
@@ -1068,7 +1068,7 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
|
|
|
1068
1068
|
|
|
1069
1069
|
- **KeyHelp** (Submodel; `KeyHelp.subscriptions` bind `?`) → a Dialog holding a narrow Card titled "Keys": the application's own words when it gives `toIntroView` (what the covered page would say that the reader still needs, a sync notice and its retry), a text Field ("Find a key", focused on open) that finds a key by its key or its words, then a Keymap of every binding given, under its group, each key listed once under the command it gives, and a note saying that keys wait while you type in a field and, when sequences exist, that a sequence's keys are typed one straight after the other and any other key lets go of the first. That is what Foldkit's key bindings do; any other pause (while a card is held, while a menu is open) is the application's to make, so the note does not claim it. `?` opens it and so does `KeyHelp.open` from the parent's update (a button, a palette entry). Render it once per page, with the bindings in force on that page. `Closed` reports the reader closing it (Escape, the veil, a close button); opening it reports nothing, by `?` or by `open`, so read `KeyHelp.isOpen` to keep another dialog from opening over the help.
|
|
1070
1070
|
- **KeyHint** → `kbd.key-hint[aria-hidden="true"]` (Button's and a menu item's `keyHint`), a key sequence printed as the help prints it: arrows as arrows, a sequence of characters as one word (`["G", "G"]` prints gg; named keys keep their space), `Mod+K` as ⌘K or Ctrl+K, Shift as ⇧ or Shift+. The platform lives in the application's Model as `KeyHint.KeyPlatform`, `"Other"` until `KeyHint.detectKeyPlatform` runs once from `init`, so a server render prints Ctrl. The same notation is exported as `KeyHint.keyLabel(keys, platform)`, for anything else that names a key (a command palette's hint).
|
|
1071
|
-
- **A list's cursor** is the application's: it keeps the current item in its Model, sets `aria-current="true"` and a roving `tabindex` (0 on the current item, -1 elsewhere) on that row's `div.row` or on that board card (`Board.cardView`'s `attributes`), and binds j and k (and ↓ ↑) to move it, `g g` and G (Home and End) to go to the ends, in an outline h and l to fold and unfold or step to the parent and the first child, on a board h and l to cross columns, and Enter to open, with the commands on the current item (`d d` removes it) beside them. A move focuses the item and scrolls it into view by the least distance; when the current item leaves (filtered, removed, folded away) the cursor takes its place. While a Board holds a card, the arrows and Space are the move's: turn the page's own bindings for them off.
|
|
1071
|
+
- **A list's cursor** is the application's: it keeps the current item in its Model, sets `aria-current="true"` and a roving `tabindex` (0 on the current item, -1 elsewhere) on that row's `div.row` or on that board card (`Board.cardView`'s `attributes`), and binds j and k (and ↓ ↑) to move it, `g g` and G (Home and End) to go to the ends, in an outline h and l to fold and unfold or step to the parent and the first child, on a board h and l to cross columns, and Enter to open, with the commands on the current item (`d d` removes it) beside them. A move focuses the item and scrolls it into view by the least distance; when the current item leaves (filtered, removed, folded away) the cursor takes its place. While a Board holds a card, the arrows and Space are the move's: turn the page's own bindings for them off. A keyboard-driven list of Rows is a treegrid (`Rows.view`'s `isTreegrid`, named by its `label`), so a screen reader hands the list its arrows and letters instead of taking them for browsing: each row carries its place (`treegrid`: its level, its position among its siblings, how many there are, and whether it is expanded when it folds) and a name in its `attributes`.
|
|
1072
1072
|
|
|
1073
1073
|
### Tips and toolbars
|
|
1074
1074
|
|
|
@@ -1084,9 +1084,9 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
|
|
|
1084
1084
|
|
|
1085
1085
|
### Rows and the outline
|
|
1086
1086
|
|
|
1087
|
-
**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), `div.grow` holding the text (a `label.grow` when it is the label of the row's box; `Rows.titleView` → `button.title`, underlined on hover, `.done-title` when done), `span.slot` for chips, marks and due days, the row's actions, and `span.tools` (`tools`, icon buttons) resting at 55% opacity until hover or focus-within (hidden on phones). Hover and focus-within tint the row head with `background-color` only, so the indent guides survive; the cursor row takes neither. A keyboard-driven list 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
|
|
1087
|
+
**Rows** → `ul.rows` (`.tree` for an outline); each row, `Rows.rowView` → `li > div.row` (`.context`) with an optional nested `ul.rows`. A context row (outside a filter, printed only to keep a match on its chain) is muted and carries no chips, and since muting is colour alone it ends its text with `span.sr-only` " (context)", which a selectable row's box takes into its name. A 34px flex row (`5px 12px`, 8px gap): an 18px glyph cell (`Rows.glyphView` → `span.glyph`, or `Rows.toggleView` → `button.glyph[aria-expanded]` with the ring glyph open or closed, named "Rows under" its label whether open or not), `div.grow` holding the text (a `label.grow` when it is the label of the row's box; `Rows.titleView` → `button.title`, underlined on hover, `.done-title` when done), `span.slot` for chips, marks and due days, the row's actions, and `span.tools` (`tools`, icon buttons) resting at 55% opacity until hover or focus-within (hidden on phones). Hover and focus-within tint the row head with `background-color` only, so the indent guides survive; the cursor row takes neither. A keyboard-driven list is a treegrid: `ul.rows[role="treegrid"]` named by its label, each `li` and nested `ul` stepping aside (`role="none"`), each `div.row[role="row"]` carrying `aria-level`, `aria-posinset`, `aria-setsize` and, on a row that folds, `aria-expanded`, and each of its parts a `role="gridcell"` (the box's `span.row-check`, the text, `span.slot`, `span.tools`, and `span.cell` around the glyph and around the actions, laid out as the row lays them out). It puts its cursor on `div.row` (`aria-current="true"`, a roving `tabindex`), which the stylesheet outlines (see "The cursor" below), through `Rows.rowView`'s `attributes`, which go on `div.row` rather than its `li` and also carry the row's name and its key handlers (never a class). A list no keyboard drives stays `ul > li`; a row there named for a screen reader takes `role="group"` with its name. Nested lists indent 26px on a 1px rule guide. **InlineInput** (Submodel; its `init` focuses and selects the field, so fold it in with `Update.foldChildInit`) → `input.inline-input` swaps in for text at exactly `1lh + 1px` so the row never moves. Enter or leaving the field reports new, non-empty text, trimmed, as `Committed`; an untouched field reports nothing, so a remote edit is never written back. The field then holds `readonly` and `aria-busy` (muted, still focused) until the parent answers with `InlineInput.succeeded` or `InlineInput.failed`: a failure keeps the field and its text for another try and, when it is a refusal with a reason, says why through `InlineInput.errorView`, which the field points at (`aria-invalid`, a red rule, and `aria-describedby`); the parent reads the held reason with `InlineInput.maybeError`. The parent's own `error` (the item's parent was deleted while editing) marks the field the same way, given to the view and to `errorView` alike, and shows before a refused save's reason. `Ended` says the edit is over (`"Enter"`, `"Blur"` or `"Escape"`, and `"Blur"` when focus left while the save was out) with the id the edit's last save returned (`maybeLastSavedId`, the item a repeat added last); the parent moves focus on `"Enter"` and `"Escape"` only, so a clicked destination keeps it. `isRepeating` (adding several) empties the field after each saved Enter instead of ending; Enter on an empty one ends. The text lives in the Submodel's Model, so it outlives a render. Escape is stopped from closing a dialog around it; an IME's Enter and Escape stay with the IME; removing the field writes nothing. **EditableTitle** (Submodel; it reports `Committed`, which the parent answers with `EditableTitle.succeeded` or `failed`) → `button.title-display` ⇄ `input.inline-input.title-edit` at 16px/600; Enter and Escape put focus back on the title.
|
|
1088
1088
|
|
|
1089
|
-
**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 rule, not a ring. A board's cursor card is inverted instead (see Board).
|
|
1089
|
+
**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).
|
|
1090
1090
|
|
|
1091
1091
|
**Selectable rows:** a row's `select` puts `span.row-check > button[role="checkbox"][aria-checked]`, built on `@foldkit/ui`'s Checkbox, at the row's left edge: the cell is the row's full height and all of it the box's target, and the text is the box's label (`label.grow`, which names it through `aria-labelledby`), so a click there toggles too. A row whose text holds a button or a link names the box with `label` instead (a `span.sr-only` in the cell, the text a plain `div.grow`), since a label's click is on all of its text. `select.id` is the stem of the label's id (`<id>-label`); the box has no id. Checked, the box's cell inverts: an ink square at the edge with the box in paper inside. Only the cell (the whole row inverts only as the cursor): inversion marks the one current thing, and half a list inverted reads as a barcode that buries its titles and chips. **SelectAll** (a View over `@foldkit/ui`'s Checkbox) → `div.row.bulk > span.row-check + div.grow > span.bulk-count`, then `span.bulk-acts`: the head above a selectable list in a flush panel, over a 1px rule: a box that is checked, clear or mixed (`aria-checked="mixed"`, a 2px bar; a mixed box selects all), named "Select all" by a `span.sr-only` in its cell and described by the count ("3 of 12 selected") in the 11px pixel face, muted until something is selected, and the bulk actions (small Buttons), which always render so the head never shifts and focus never drops when the selection clears; the caller marks them `aria-disabled` while nothing is selected. Only the box toggles: a click on the count does nothing. With no rows the box is `aria-disabled`, muted and still in the keyboard. `id` is the stem of the name's and the count's ids (`<id>-label`, `<id>-description`); the box has no id.
|
|
1092
1092
|
|
|
@@ -1096,11 +1096,11 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
|
|
|
1096
1096
|
|
|
1097
1097
|
### Inputs / Fields
|
|
1098
1098
|
|
|
1099
|
-
- **Field** (a View over `@foldkit/ui`'s Input) → `div.field (.boxed, .mono) > label + input + p.field-help`, the label naming the input and the help describing it, the input's `type` the platform's (`"date"` for the browser's date control) when given. **SelectField** (over `@foldkit/ui`'s Select) → `div.field > label + select + p.field-help`, its options in order and a group's under an `optgroup` heading, an option that is listed but not choosable `disabled`; **TextareaField** (over `@foldkit/ui`'s Textarea) → `div.field > label + textarea + p.field-help`, opening at `rows` lines. Both name, describe and mark their control as a Field does
|
|
1099
|
+
- **Field** (a View over `@foldkit/ui`'s Input) → `div.field (.boxed, .mono) > label + input + p.field-error + p.field-help`, the label naming the input and the help describing it; a failed field prints why in `p.field-error` (sans, Urgent Red) under the control, which is `aria-invalid` and described by it before its help, and the ErrorSummary above the fields says it again; a required field's control is `aria-required`; the input's `type` the platform's (`"date"` for the browser's date control) when given. **SelectField** (over `@foldkit/ui`'s Select) → `div.field > label + select + p.field-error + p.field-help`, its options in order and a group's under an `optgroup` heading, an option that is listed but not choosable `disabled`; **TextareaField** (over `@foldkit/ui`'s Textarea) → `div.field (.mono) > label + textarea + p.field-error + p.field-help`, opening at `rows` lines. Both name, describe and mark their control as a Field does. **Fieldset** → `fieldset.field > legend.field-label`, then the controls, `p.field-error` and `p.field-help`, its error and its help linked with `aria-describedby`; **FieldGrid** → `div.field-grid` (auto-fit, 160px minimum); **Fields** → `fieldset.stack.gap-m`: a form's fields as one column `--space-m` (14px) apart, a body's gap, on the layout primitives with no rule of its own. `isBusy` (a save in flight) sets `disabled` and `aria-busy` on it, so every control holds at once and nothing changes under the save; the submit Button stays outside it, `isBusy`, so it keeps focus. A disabled control prints its value in Muted Ink; a boxless one also drops its dotted baseline (the mark of a place to type), a boxed one or a select mutes its box and caret.
|
|
1100
1100
|
- **Label:** 10px uppercase 0.1em Muted Ink, 4px above; turns red when the field is invalid, as a fieldset's legend does when a control in it is.
|
|
1101
|
-
- **Boxless (default):** full width, sans body, no box, a 1px dotted Muted Ink baseline, `padding: 2px 0`. On keyboard the baseline becomes 2px solid ink; invalid, 2px solid red.
|
|
1101
|
+
- **Boxless (default):** full width, sans body, no box, a 1px dotted Muted Ink baseline, `padding: 2px 0`. On keyboard the baseline becomes 2px solid ink; invalid, 2px solid red.
|
|
1102
1102
|
- **Boxed:** the field that is the whole job of its form (sign-in, a quick-add field): 2px ink border, `padding: 5px 8px`; keyboard ring 2px outside; invalid turns the border red. A date is always boxed.
|
|
1103
|
-
- **PasswordField** (a View; the caller keeps whether it shows) → a boxed Field whose control is `span.password > input + button.iconbtn.quiet[aria-pressed]`: the toggle ("Show password", pressed while shown; its glyph turns from `eye` to `eye-off`) sits inside the box at its right end, the input padded 32px clear of it. With `strength` (0 to 4, as the product measures it; the system does not) a new password shows `span.strength` 6px under the box: four short cells lit up to the reading, a `meter` named "Password strength", and its word in the age register ("too short", which an empty box is, "weak", "fair", "good", "strong"). Signing in leaves it out.
|
|
1103
|
+
- **PasswordField** (a View; the caller keeps whether it shows) → a boxed Field whose control is `span.password > input + button.iconbtn.quiet[aria-pressed]`: the toggle ("Show password", pressed while shown; each of two in one form named apart, such as "Show password again"; its glyph turns from `eye` to `eye-off`) sits inside the box at its right end, the input padded 32px clear of it. With `strength` (0 to 4, as the product measures it; the system does not) a new password shows `span.strength` 6px under the box (under its `p.field-error` when it failed): four short cells lit up to the reading, a `meter` named "Password strength", and its word in the age register ("too short", which an empty box is, "weak", "fair", "good", "strong"). Signing in leaves it out.
|
|
1104
1104
|
- **Textarea** (TextareaField's): boxless, 13px/1.5 sans, grows with content from 96px, vertical resize with an ink corner grip. **Mono:** system mono at 12px.
|
|
1105
1105
|
- **Select** (SelectField's): `appearance: base-select` where supported; 30px minimum, 2px ink border, 11px pixel face, a CSS-drawn caret; the picker is paper with a 2px border and the panel shadow. Invalid, its box turns red.
|
|
1106
1106
|
- **Choice** (a View over `@foldkit/ui`'s Checkbox) → `div.choice > button.choice-box[role="checkbox"][aria-checked] + span > span.choice-name + span.choice-note`; **RadioChoices** (Submodel over `@foldkit/ui`'s RadioGroup) → `div.choices[role="radiogroup"] > button.choice[role="radio"][aria-checked] > span.choice-box + span > span.choice-name + span.choice-note`: the option is the button itself, so a click anywhere on its line chooses it and the focus ring is around the line; `RadioChoices.firstOptionId` is the id of the first option, for a link to the group (an ErrorSummary's); **Choices** → `div.choices (.inline)`. 14px ink-bordered squares with a 6px painted cell (radio: 8px diamond). Invalid (`aria-invalid` on the radiogroup, since a radio does not take it), each box and choice's name turns red.
|
|
@@ -1130,7 +1130,7 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
|
|
|
1130
1130
|
- **CodeSurface** → `pre.code-surface[tabindex="0"][role="group"]`, named by its label: a 2px ink-bordered head-tinted block in 12px/1.6 system mono, `10px 12px`, `tab-size: 2`, wrapping. It takes a string or a highlighter's spans: `tok-keyword`; `tok-string`, `tok-string2`, `tok-url`; `tok-number`, `tok-bool`, `tok-atom`, `tok-literal`; `tok-definition`, `tok-variableName2`, `tok-macroName`, `tok-link` (underlined); `tok-typeName`, `tok-className`, `tok-namespace`; `tok-comment`, `tok-meta`, `tok-punctuation` take their syntax roles; `tok-heading` and `tok-strong` are bold, `tok-emphasis` italic, `tok-inserted` green ink, `tok-deleted` and `tok-invalid` red ink (invalid on a dotted underline). Anything unnamed stays ink. **CodeEditor** (a View over `@foldkit/ui`'s Textarea) → `textarea.code-surface`, spelling, capitals and autocomplete off: 220px minimum, grows to 60vh, vertically resizable, scrolls sideways (wraps on phones).
|
|
1131
1131
|
- **Secret** → `div.secret > code[role="group"] + button.btn.small`, the code named by its label: the same box at `6px 6px 6px 12px` with a scrolling nowrap code at 0.02em (wrapping anywhere on a phone, so the end is never hidden) and, in its `actions`, a copy button whose word turns "copied". **CopyButton** (Submodel; `CopyButton.isCopied` is for a parent that follows the copy) → the quiet copy glyph (a check once copied) or, as `"Button"`, a small button whose word turns "copied", for a beat; either says it to screen readers, and a copy that fails says so. It writes through Effect's `Clipboard` service: the application provides `Clipboard.layer` from `@effect/platform-browser` in its `resources`. A `div.save-row` with a `span.push` holds what follows it (the "I've stored it" commit).
|
|
1132
1132
|
- **History** → `ol.history-list (.checks) > li (.current, [aria-current="true"]) > span.v + span.who + span.what (.failed)`: ruled rows, the version in 11px pixel face in a fixed `4ch + 8px` box (inverted when current, so nothing moves, a Time in it taking the paper too), who in 10px muted at 10ch (20ch for checks), what in sans, a failed line red with "(failed)" after it for screen readers (`span.sr-only`), so the state never rests on colour. `isWrapped` → `ol.history-list.wrapped` (never `.wrap`, which is Wrap's row), for an activity log whose words matter more than a tidy column, compact enough for a rail's panel: when (a whole timestamp, its box as wide as it, muted and flush with the what under it, padded `0 4px` and inverted only when current) and who share the first line with the action pushed to its end, and what runs on its own line below in the note size (12px/1.45 sans, in ink), wrapping anywhere. Entries are `6px 0` apart over 1px rules with a 2px gap between their lines; there is no rule above the first and no padding before the first or after the last, so the list starts where a body's content starts.
|
|
1133
|
-
- **LineDiff** → `div.diff-wrap > table.diff > tr.add | tr.del | tr.same | tr.fold`: a Myers line diff (jsdiff's `diff`, a runtime dependency) in a 2px ink box on paper that scrolls sideways; 12px/1.6 system-mono lines, each with two 10px pixel-face line numbers behind a 1px rule, a sign column, and the line. Added lines are Head Tint behind an ink `+`; deleted lines muted behind a red `−`; unchanged runs beyond two lines of context fold into a head-tinted row labelled in 10px uppercase On Head. On phones lines wrap.
|
|
1133
|
+
- **LineDiff** → `div.diff-wrap > table.diff > tr.add | tr.del | tr.same | tr.fold`: a Myers line diff (jsdiff's `diff`, a runtime dependency) in a 2px ink box on paper that scrolls sideways; 12px/1.6 system-mono lines, each with two 10px pixel-face line numbers behind a 1px rule, a sign column, and the line. Added lines are Head Tint behind an ink `+`; deleted lines muted behind a red `−`; unchanged runs beyond two lines of context fold into a head-tinted row labelled in 10px uppercase On Head. On phones lines wrap. `LineDiff.patchView` draws the same table from one file's unified diff, the lines between its hunks folded into the counted row; anything else prints a note line.
|
|
1134
1134
|
|
|
1135
1135
|
### Meters and cells
|
|
1136
1136
|
|
|
@@ -1161,7 +1161,7 @@ All three keep their space from the first paint (`size`: `Media.FrameSize.Ratio(
|
|
|
1161
1161
|
|
|
1162
1162
|
### Reading
|
|
1163
1163
|
|
|
1164
|
-
**Sheet** → `article.sheet > div.sheet-head > h1.sheet-title + div.sheet-acts`, then `p.byline`, then the body, usually **Prose** → `div.prose`. The sheet pads 28px 32px 36px (20px 16px 28px on phones); the byline is 10px pixel meta over a 1px rule. Prose: paragraphs 1em apart; h2 (and an h1, styled as h2) 2em above, h3, h4 (pixel caps), h5 (bold, 13px) and h6 (muted pixel label) 24px above; a document rendered inside a titled container shifts its headings to sit under that title (under a Dialog's h2, Markdown's `#` becomes h3); square list bullets in Muted Ink and ordered-list numbers in the 12px pixel face; inline code on head tint at 0.85em; code blocks as code surfaces at 12.5px; tables in a 2px ink box at 14px; a 1px Rule Grey `hr`; blockquotes muted behind a 1px ink rule. Links are ink with a 1px underline offset 3px that thickens to 2px on hover; `a.missing` (a page that does not exist) is red on a dotted red underline. **Toc** → `nav > ul.toc > li (.l3) > a[href="#id"][aria-current]`, an entry with detail holding `span.toc-entry` (its words over the detail, 2px apart): plain links to the page's headings (`html` keeps `scroll-padding-top` clear of the sticky top bar) in 13px sans (12px muted for h3, indented), one 6px ink cell before the heading being read; every entry keeps the slot so nothing shifts. The heading being read is the caller's, kept by a Subscription entry, `Toc.readingPositionEntry`.
|
|
1164
|
+
**Sheet** → `article.sheet > div.sheet-head > h1.sheet-title + div.sheet-acts`, then `p.byline`, then the body, usually **Prose** → `div.prose`. A sheet is framed as a panel is (paper, a 2px ink box, the 5px print shadow) and takes a panel's place on the page, never sitting inside one: its h1 opens the page, where a panel's h2 title would come before it. What a panel's title bar would say (the path, "earlier version") goes in the byline or the actions. The sheet pads 28px 32px 36px (20px 16px 28px on phones); the byline is 10px pixel meta over a 1px rule. Prose: paragraphs 1em apart; h2 (and an h1, styled as h2) 2em above, h3, h4 (pixel caps), h5 (bold, 13px) and h6 (muted pixel label) 24px above; a document rendered inside a titled container shifts its headings to sit under that title (under a Dialog's h2, Markdown's `#` becomes h3); square list bullets in Muted Ink and ordered-list numbers in the 12px pixel face; inline code on head tint at 0.85em; code blocks as code surfaces at 12.5px; tables in a 2px ink box at 14px; a 1px Rule Grey `hr`; blockquotes muted behind a 1px ink rule. Links are ink with a 1px underline offset 3px that thickens to 2px on hover; `a.missing` (a page that does not exist) is red on a dotted red underline. **Toc** → `nav > ul.toc > li (.l3) > a[href="#id"][aria-current]`, an entry with detail holding `span.toc-entry` (its words over the detail, 2px apart): plain links to the page's headings (`html` keeps `scroll-padding-top` clear of the sticky top bar) in 13px sans (12px muted for h3, indented), one 6px ink cell before the heading being read; every entry keeps the slot so nothing shifts. The heading being read is the caller's, kept by a Subscription entry, `Toc.readingPositionEntry`.
|
|
1165
1165
|
|
|
1166
1166
|
### Signature: the invert flash and the flash word
|
|
1167
1167
|
|
|
@@ -3,6 +3,10 @@ import type { Html, HtmlBuilder } from "foldkit/html";
|
|
|
3
3
|
export type ViewConfig = Readonly<{
|
|
4
4
|
/** Pixel caps: what is missing (`"Nothing queued"`). */
|
|
5
5
|
title: string;
|
|
6
|
+
/** Makes the title a heading at this level, for an empty state that is the page's or the
|
|
7
|
+
* section's main content (a gate, a missing page); a `strong` when not given. It looks the
|
|
8
|
+
* same either way. */
|
|
9
|
+
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
6
10
|
/** One sentence: how it gets filled. */
|
|
7
11
|
body?: ReadonlyArray<Html | string>;
|
|
8
12
|
/** What fills it: a button under the sentence. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emptyState.d.ts","sourceRoot":"","sources":["../../../src/emptyState/emptyState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAItD,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACpC,kDAAkD;IAClD,OAAO,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;CACxC,CAAC,CAAC;AAEH,yFAAyF;AACzF,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,
|
|
1
|
+
{"version":3,"file":"emptyState.d.ts","sourceRoot":"","sources":["../../../src/emptyState/emptyState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAItD,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd;;2BAEuB;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACrC,wCAAwC;IACxC,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACpC,kDAAkD;IAClD,OAAO,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;CACxC,CAAC,CAAC;AAEH,yFAAyF;AACzF,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAa3E,CAAC"}
|
|
@@ -2,9 +2,11 @@ import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
|
|
|
2
2
|
import { Array } from "effect";
|
|
3
3
|
/** What a panel says when it has nothing to show, centred where its content would be. */
|
|
4
4
|
export const view = (config, h) => {
|
|
5
|
-
const { title, body = [], actions = [] } = config;
|
|
5
|
+
const { title, headingLevel, body = [], actions = [] } = config;
|
|
6
6
|
return __foldkitBrandViewResult((h.div([h.Class("empty")], [
|
|
7
|
-
|
|
7
|
+
headingLevel === undefined
|
|
8
|
+
? h.strong([h.Class("empty-title")], [title])
|
|
9
|
+
: h[`h${headingLevel}`]([h.Class("empty-title")], [title]),
|
|
8
10
|
...Array.match(body, { onEmpty: () => __foldkitBrandViewResult(([]), "@gjermundgaraba-designs/pixel-paper/src/emptyState/emptyState.js#onEmpty"), onNonEmpty: (sentence) => __foldkitBrandViewResult(([h.p([], sentence)]), "@gjermundgaraba-designs/pixel-paper/src/emptyState/emptyState.js#onNonEmpty") }),
|
|
9
11
|
...actions,
|
|
10
12
|
])), "@gjermundgaraba-designs/pixel-paper/src/emptyState/emptyState.js#view");
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
1
|
+
import type { Html, HtmlBuilder, TextareaAttribute } from "foldkit/html";
|
|
2
2
|
import type { Attributes } from "../attributes/attributes.ts";
|
|
3
|
+
/** The red sentence under a field's control that says why it failed, given an error. */
|
|
4
|
+
export declare const errorView: <Message>(id: string, error: string | undefined, h: HtmlBuilder<Message>) => ReadonlyArray<Html>;
|
|
5
|
+
/** Points the control at its error, then at its help line when it has one. After the
|
|
6
|
+
* `@foldkit/ui` attributes, whose `aria-describedby` names the help line alone; every
|
|
7
|
+
* `@foldkit/ui` control names that line `<id>-description`, as Input does. */
|
|
8
|
+
export declare const describedByError: <Message>(id: string, error: string | undefined, hasHelp: boolean, h: HtmlBuilder<Message>) => ReadonlyArray<TextareaAttribute<Message>>;
|
|
3
9
|
/** Configuration for rendering a text field with `view`. */
|
|
4
10
|
export type ViewConfig<Message> = Readonly<{
|
|
5
11
|
id: string;
|
|
@@ -8,8 +14,10 @@ export type ViewConfig<Message> = Readonly<{
|
|
|
8
14
|
onInput: (value: string) => Message;
|
|
9
15
|
/** A sans sentence under the control. */
|
|
10
16
|
help?: string;
|
|
11
|
-
/**
|
|
12
|
-
|
|
17
|
+
/** Why the value was refused: a red sentence under the control, which the control is
|
|
18
|
+
* described by, with red on its rule and its label (`aria-invalid`). Say it again in an
|
|
19
|
+
* ErrorSummary above the form. */
|
|
20
|
+
error?: string | undefined;
|
|
13
21
|
/** The form is not sent without it: screen readers hear "required" (`aria-required`). */
|
|
14
22
|
isRequired?: boolean;
|
|
15
23
|
/** A 2px ink box: the field that is the whole job of its form. A date is always boxed. */
|
|
@@ -24,8 +32,8 @@ export type ViewConfig<Message> = Readonly<{
|
|
|
24
32
|
inputAttributes?: Attributes<Message>;
|
|
25
33
|
}>;
|
|
26
34
|
/** A labelled text input with its help line, built on `@foldkit/ui`'s `Input`: the label
|
|
27
|
-
* names the input, the help line describes it (`aria-describedby`), `
|
|
28
|
-
* `aria-invalid` and `isRequired` sets `aria-required`.
|
|
29
|
-
* baseline that turns solid ink on focus. */
|
|
35
|
+
* names the input, the help line describes it (`aria-describedby`), an `error` sets
|
|
36
|
+
* `aria-invalid` and describes it before the help, and `isRequired` sets `aria-required`.
|
|
37
|
+
* The input is boxless, a dotted baseline that turns solid ink on focus. */
|
|
30
38
|
export declare const view: <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Message>) => Html;
|
|
31
39
|
//# sourceMappingURL=field.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../../src/field/field.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../../src/field/field.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAM9D,wFAAwF;AACxF,eAAO,MAAM,SAAS,GAAI,OAAO,MAC3B,MAAM,SACH,MAAM,GAAG,SAAS,KACtB,WAAW,CAAC,OAAO,CAAC,KACtB,aAAa,CAAC,IAAI,CACmE,CAAC;AAEzF;;+EAE+E;AAC/E,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAClC,MAAM,SACH,MAAM,GAAG,SAAS,WAChB,OAAO,KACb,WAAW,CAAC,OAAO,CAAC,KACtB,aAAa,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAGqD,CAAC;AAIjG,4DAA4D;AAC5D,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACpC,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;uCAEmC;IACnC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,yFAAyF;IACzF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;iBACa;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;iBACa;IACb,eAAe,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;CACvC,CAAC,CAAC;AAEH;;;6EAG6E;AAC7E,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IA4CpF,CAAC"}
|
package/dist/src/field/field.js
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
|
|
2
2
|
import { Input as UiInput } from "@foldkit/ui";
|
|
3
|
+
// ERROR
|
|
4
|
+
const errorId = (id) => __foldkitBrandViewResult((`${id}-error`), "@gjermundgaraba-designs/pixel-paper/src/field/field.js#errorId");
|
|
5
|
+
/** The red sentence under a field's control that says why it failed, given an error. */
|
|
6
|
+
export const errorView = (id, error, h) => __foldkitBrandViewResult((error === undefined ? [] : [h.p([h.Id(errorId(id)), h.Class("field-error")], [error])]), "@gjermundgaraba-designs/pixel-paper/src/field/field.js#errorView");
|
|
7
|
+
/** Points the control at its error, then at its help line when it has one. After the
|
|
8
|
+
* `@foldkit/ui` attributes, whose `aria-describedby` names the help line alone; every
|
|
9
|
+
* `@foldkit/ui` control names that line `<id>-description`, as Input does. */
|
|
10
|
+
export const describedByError = (id, error, hasHelp, h) => __foldkitBrandViewResult((error === undefined
|
|
11
|
+
? []
|
|
12
|
+
: [h.AriaDescribedBy(hasHelp ? `${errorId(id)} ${UiInput.descriptionId(id)}` : errorId(id))]), "@gjermundgaraba-designs/pixel-paper/src/field/field.js#describedByError");
|
|
3
13
|
/** A labelled text input with its help line, built on `@foldkit/ui`'s `Input`: the label
|
|
4
|
-
* names the input, the help line describes it (`aria-describedby`), `
|
|
5
|
-
* `aria-invalid` and `isRequired` sets `aria-required`.
|
|
6
|
-
* baseline that turns solid ink on focus. */
|
|
14
|
+
* names the input, the help line describes it (`aria-describedby`), an `error` sets
|
|
15
|
+
* `aria-invalid` and describes it before the help, and `isRequired` sets `aria-required`.
|
|
16
|
+
* The input is boxless, a dotted baseline that turns solid ink on focus. */
|
|
7
17
|
export const view = (config, h) => {
|
|
8
|
-
const { id, label, value, onInput, help,
|
|
18
|
+
const { id, label, value, onInput, help, error, isRequired = false, isBoxed = false, isMono = false, type, inputAttributes = [], } = config;
|
|
9
19
|
return __foldkitBrandViewResult((UiInput.view({
|
|
10
20
|
id,
|
|
11
21
|
value,
|
|
12
22
|
onInput,
|
|
13
|
-
isInvalid,
|
|
23
|
+
isInvalid: error !== undefined,
|
|
14
24
|
...(type !== undefined && { type }),
|
|
15
25
|
hasDescription: help !== undefined,
|
|
16
26
|
toView: (groups) => __foldkitBrandViewResult((h.div([h.Class(`field${isBoxed || type === "date" ? " boxed" : ""}${isMono ? " mono" : ""}`)], [
|
|
@@ -19,8 +29,10 @@ export const view = (config, h) => {
|
|
|
19
29
|
h.input([
|
|
20
30
|
...inputAttributes,
|
|
21
31
|
...groups.input,
|
|
32
|
+
...describedByError(id, error, help !== undefined, h),
|
|
22
33
|
...(isRequired ? [h.AriaRequired(true)] : []),
|
|
23
34
|
]),
|
|
35
|
+
...errorView(id, error, h),
|
|
24
36
|
...(help === undefined
|
|
25
37
|
? []
|
|
26
38
|
: [h.p([...groups.description, h.Class("field-help")], [help])]),
|
|
@@ -7,11 +7,16 @@ export type ViewConfig = Readonly<{
|
|
|
7
7
|
label: string;
|
|
8
8
|
/** A sans sentence under the controls; it describes the group. */
|
|
9
9
|
help?: string;
|
|
10
|
+
/** Why the choice was refused: a red sentence under the controls, which the group is
|
|
11
|
+
* described by, before its help. Mark the control itself invalid too (RadioChoices'
|
|
12
|
+
* `isInvalid`), which turns the legend red. Say it again in an ErrorSummary above the
|
|
13
|
+
* form. */
|
|
14
|
+
error?: string | undefined;
|
|
10
15
|
/** The controls: Choices, a Segmented. */
|
|
11
16
|
body: ReadonlyArray<Html | string>;
|
|
12
17
|
}>;
|
|
13
18
|
/** A group of controls under one name, built on `@foldkit/ui`'s Fieldset: a `<fieldset>`
|
|
14
|
-
* named by its `<legend>` and described by its
|
|
15
|
-
* turns red while a control in it is invalid. */
|
|
19
|
+
* named by its `<legend>` and described by its error and its help line
|
|
20
|
+
* (`aria-describedby`). The legend turns red while a control in it is invalid. */
|
|
16
21
|
export declare const view: <Message>(config: ViewConfig, h: HtmlBuilder<Message>) => Html;
|
|
17
22
|
//# sourceMappingURL=fieldset.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fieldset.d.ts","sourceRoot":"","sources":["../../../src/fieldset/fieldset.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"fieldset.d.ts","sourceRoot":"","sources":["../../../src/fieldset/fieldset.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMtD,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,qFAAqF;IACrF,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;gBAGY;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,0CAA0C;IAC1C,IAAI,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;CACpC,CAAC,CAAC;AAEH;;mFAEmF;AACnF,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IA0B3E,CAAC"}
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
|
|
2
2
|
import { Fieldset as UiFieldset } from "@foldkit/ui";
|
|
3
|
+
import { describedByError, errorView } from "../field/field.js";
|
|
3
4
|
/** A group of controls under one name, built on `@foldkit/ui`'s Fieldset: a `<fieldset>`
|
|
4
|
-
* named by its `<legend>` and described by its
|
|
5
|
-
* turns red while a control in it is invalid. */
|
|
5
|
+
* named by its `<legend>` and described by its error and its help line
|
|
6
|
+
* (`aria-describedby`). The legend turns red while a control in it is invalid. */
|
|
6
7
|
export const view = (config, h) => {
|
|
7
|
-
const { id, label, help, body } = config;
|
|
8
|
+
const { id, label, help, error, body } = config;
|
|
8
9
|
return __foldkitBrandViewResult((UiFieldset.view({
|
|
9
10
|
id,
|
|
10
11
|
hasDescription: help !== undefined,
|
|
11
|
-
toView: (groups) => __foldkitBrandViewResult((h.fieldset([
|
|
12
|
+
toView: (groups) => __foldkitBrandViewResult((h.fieldset([
|
|
13
|
+
...groups.fieldset,
|
|
14
|
+
...describedByError(id, error, help !== undefined, h),
|
|
15
|
+
h.Class("field"),
|
|
16
|
+
], [
|
|
12
17
|
h.legend([...groups.legend, h.Class("field-label")], [label]),
|
|
13
18
|
...body,
|
|
19
|
+
...errorView(id, error, h),
|
|
14
20
|
...(help === undefined
|
|
15
21
|
? []
|
|
16
22
|
: [h.p([...groups.description, h.Class("field-help")], [help])]),
|
|
@@ -1,43 +1,38 @@
|
|
|
1
|
-
import { Schema } from "effect";
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
export type LineKind = typeof LineKind.Type;
|
|
5
|
-
/** One line of a diff, numbered on the side or sides it is on. */
|
|
6
|
-
export declare const DiffLine: Schema.Struct<{
|
|
7
|
-
readonly kind: Schema.Literals<readonly ["Same", "Added", "Removed"]>;
|
|
8
|
-
readonly text: Schema.String;
|
|
9
|
-
readonly maybeOldNumber: Schema.Option<Schema.Number>;
|
|
10
|
-
readonly maybeNewNumber: Schema.Option<Schema.Number>;
|
|
11
|
-
}>;
|
|
12
|
-
export type DiffLine = typeof DiffLine.Type;
|
|
13
|
-
/** A row of a folded diff: a line, or a run of two or more unchanged lines folded into a
|
|
14
|
-
* count, with the run's first line. */
|
|
1
|
+
import { Option, Schema } from "effect";
|
|
2
|
+
/** A row of a diff: a line, numbered on the side or sides it is on, or a run of unchanged
|
|
3
|
+
* lines folded into a count, known by its first line's old number. */
|
|
15
4
|
export declare const DiffRow: import("foldkit/schema").TaggedUnion<{
|
|
16
|
-
readonly
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
readonly Same: {
|
|
6
|
+
text: Schema.String;
|
|
7
|
+
oldNumber: Schema.Number;
|
|
8
|
+
newNumber: Schema.Number;
|
|
9
|
+
};
|
|
10
|
+
readonly Added: {
|
|
11
|
+
text: Schema.String;
|
|
12
|
+
newNumber: Schema.Number;
|
|
13
|
+
};
|
|
14
|
+
readonly Removed: {
|
|
15
|
+
text: Schema.String;
|
|
16
|
+
oldNumber: Schema.Number;
|
|
23
17
|
};
|
|
24
18
|
readonly Fold: {
|
|
25
19
|
count: Schema.Number;
|
|
26
|
-
|
|
27
|
-
readonly kind: Schema.Literals<readonly ["Same", "Added", "Removed"]>;
|
|
28
|
-
readonly text: Schema.String;
|
|
29
|
-
readonly maybeOldNumber: Schema.Option<Schema.Number>;
|
|
30
|
-
readonly maybeNewNumber: Schema.Option<Schema.Number>;
|
|
31
|
-
}>;
|
|
20
|
+
firstOldNumber: Schema.Number;
|
|
32
21
|
};
|
|
33
22
|
}>;
|
|
34
23
|
export type DiffRow = typeof DiffRow.Type;
|
|
24
|
+
/** What a line did between the two sides. */
|
|
25
|
+
export type LineKind = "Same" | "Added" | "Removed";
|
|
35
26
|
/** The line diff of two texts (Myers, through jsdiff), each line numbered on its side. A
|
|
36
27
|
* missing final newline is not a change. */
|
|
37
|
-
export declare const
|
|
38
|
-
/** Whether
|
|
39
|
-
export declare const hasChanges: (
|
|
28
|
+
export declare const diffRowsOf: (before: string, after: string) => ReadonlyArray<DiffRow>;
|
|
29
|
+
/** Whether a line was added or removed. */
|
|
30
|
+
export declare const hasChanges: (rows: ReadonlyArray<DiffRow>) => boolean;
|
|
31
|
+
/** The rows of one file's unified diff, numbered from its hunks' headers, with the unchanged
|
|
32
|
+
* lines between hunks folded into a count. `None` unless it is one file with a hunk: text that
|
|
33
|
+
* is not a diff, a binary change and several files have no lines to show. */
|
|
34
|
+
export declare const diffRowsOfPatch: (patch: string) => Option.Option<ReadonlyArray<DiffRow>>;
|
|
40
35
|
/** The diff's rows: every change with `context` unchanged lines around it, and each longer
|
|
41
36
|
* run of unchanged lines folded into one row that counts them (a run of one stays a line). */
|
|
42
|
-
export declare const foldUnchanged: (
|
|
37
|
+
export declare const foldUnchanged: (rows: ReadonlyArray<DiffRow>, context: number) => ReadonlyArray<DiffRow>;
|
|
43
38
|
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/lineDiff/diff.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/lineDiff/diff.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,MAAM,EAAE,MAAM,EAAQ,MAAM,QAAQ,CAAC;AAK5D;uEACuE;AACvE,eAAO,MAAM,OAAO;;QACV,IAAI;QAAiB,SAAS;QAAiB,SAAS;;;QACvD,IAAI;QAAiB,SAAS;;;QAC5B,IAAI;QAAiB,SAAS;;;QACjC,KAAK;QAAiB,cAAc;;EAC5C,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAC;AAI1C,6CAA6C;AAC7C,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAgCpD;6CAC6C;AAC7C,eAAO,MAAM,UAAU,WAAY,MAAM,SAAS,MAAM,KAAG,aAAa,CAAC,OAAO,CAc7E,CAAC;AAEJ,2CAA2C;AAC3C,eAAO,MAAM,UAAU,SAAU,aAAa,CAAC,OAAO,CAAC,KAAG,OACiB,CAAC;AA+C5E;;8EAE8E;AAC9E,eAAO,MAAM,eAAe,UAAW,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAOjF,CAAC;AAqBJ;+FAC+F;AAC/F,eAAO,MAAM,aAAa,SAClB,aAAa,CAAC,OAAO,CAAC,WACnB,MAAM,KACd,aAAa,CAAC,OAAO,CAWvB,CAAC"}
|
|
@@ -1,59 +1,80 @@
|
|
|
1
1
|
import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
|
|
2
|
-
import { diffLines } from "diff";
|
|
2
|
+
import { diffLines, parsePatch } from "diff";
|
|
3
3
|
import { Array, Match, Option, Schema, pipe } from "effect";
|
|
4
4
|
import { defineTaggedUnion } from "foldkit/schema";
|
|
5
|
-
//
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
/** One line of a diff, numbered on the side or sides it is on. */
|
|
9
|
-
export const DiffLine = Schema.Struct({
|
|
10
|
-
kind: LineKind,
|
|
11
|
-
text: Schema.String,
|
|
12
|
-
maybeOldNumber: Schema.Option(Schema.Number),
|
|
13
|
-
maybeNewNumber: Schema.Option(Schema.Number),
|
|
14
|
-
});
|
|
15
|
-
/** A row of a folded diff: a line, or a run of two or more unchanged lines folded into a
|
|
16
|
-
* count, with the run's first line. */
|
|
5
|
+
// ROWS
|
|
6
|
+
/** A row of a diff: a line, numbered on the side or sides it is on, or a run of unchanged
|
|
7
|
+
* lines folded into a count, known by its first line's old number. */
|
|
17
8
|
export const DiffRow = defineTaggedUnion({
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
Same: { text: Schema.String, oldNumber: Schema.Number, newNumber: Schema.Number },
|
|
10
|
+
Added: { text: Schema.String, newNumber: Schema.Number },
|
|
11
|
+
Removed: { text: Schema.String, oldNumber: Schema.Number },
|
|
12
|
+
Fold: { count: Schema.Number, firstOldNumber: Schema.Number },
|
|
20
13
|
});
|
|
21
14
|
const withFinalNewline = (text) => (__foldkitBrandViewResult((text.endsWith("\n") ? text : `${text}\n`), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#withFinalNewline"));
|
|
22
15
|
const changeKind = (change) => __foldkitBrandViewResult((Match.value(change).pipe(Match.withReturnType(), Match.when({ added: true }, () => __foldkitBrandViewResult(("Added"), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous")), Match.when({ removed: true }, () => __foldkitBrandViewResult(("Removed"), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~2")), Match.orElse(() => __foldkitBrandViewResult(("Same"), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~3")))), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#changeKind");
|
|
23
|
-
const numberLine = (numbers, kind, text) =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
},
|
|
34
|
-
]), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#numberLine");
|
|
35
|
-
};
|
|
16
|
+
const numberLine = (numbers, kind, text) => __foldkitBrandViewResult((Match.value(kind).pipe(Match.withReturnType(), Match.when("Same", () => __foldkitBrandViewResult(([
|
|
17
|
+
{ old: numbers.old + 1, new: numbers.new + 1 },
|
|
18
|
+
DiffRow.Same({ text, oldNumber: numbers.old, newNumber: numbers.new }),
|
|
19
|
+
]), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~4")), Match.when("Added", () => __foldkitBrandViewResult(([
|
|
20
|
+
{ old: numbers.old, new: numbers.new + 1 },
|
|
21
|
+
DiffRow.Added({ text, newNumber: numbers.new }),
|
|
22
|
+
]), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~5")), Match.when("Removed", () => __foldkitBrandViewResult(([
|
|
23
|
+
{ old: numbers.old + 1, new: numbers.new },
|
|
24
|
+
DiffRow.Removed({ text, oldNumber: numbers.old }),
|
|
25
|
+
]), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~6")), Match.exhaustive)), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#numberLine");
|
|
36
26
|
/** The line diff of two texts (Myers, through jsdiff), each line numbered on its side. A
|
|
37
27
|
* missing final newline is not a change. */
|
|
38
|
-
export const
|
|
28
|
+
export const diffRowsOf = (before, after) => __foldkitBrandViewResult((pipe(diffLines(withFinalNewline(before), withFinalNewline(after)), Array.flatMap((change) => __foldkitBrandViewResult((Array.map(change.value.slice(0, -1).split("\n"), (text) => (__foldkitBrandViewResult(({
|
|
39
29
|
kind: changeKind(change),
|
|
40
30
|
text,
|
|
41
|
-
}), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~
|
|
42
|
-
/** Whether
|
|
43
|
-
export const hasChanges = (
|
|
31
|
+
}), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~8")))), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~7")), (lines) => __foldkitBrandViewResult((Array.mapAccum(lines, { old: 1, new: 1 }, (numbers, { kind, text }) => __foldkitBrandViewResult((numberLine(numbers, kind, text)), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~10"))), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~9"), ([, rows]) => __foldkitBrandViewResult((rows), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~11"))), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#diffRowsOf");
|
|
32
|
+
/** Whether a line was added or removed. */
|
|
33
|
+
export const hasChanges = (rows) => __foldkitBrandViewResult((Array.some(rows, (row) => __foldkitBrandViewResult((row._tag === "Added" || row._tag === "Removed"), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~12"))), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#hasChanges");
|
|
34
|
+
const LINE_KIND_BY_PREFIX = {
|
|
35
|
+
" ": "Same",
|
|
36
|
+
"+": "Added",
|
|
37
|
+
"-": "Removed",
|
|
38
|
+
};
|
|
39
|
+
// a blank context line that lost its leading space on the way (a trimming editor, a mail) is
|
|
40
|
+
// still a context line
|
|
41
|
+
const kindOf = (line) => __foldkitBrandViewResult((line === "" ? Option.some("Same") : Option.fromUndefinedOr(LINE_KIND_BY_PREFIX[line.charAt(0)])), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#kindOf");
|
|
42
|
+
const readHunk = ({ rows, numbers }, hunk) => {
|
|
43
|
+
const gap = hunk.oldStart - numbers.old;
|
|
44
|
+
const gapRows = gap > 0 ? [DiffRow.Fold({ count: gap, firstOldNumber: numbers.old })] : [];
|
|
45
|
+
return __foldkitBrandViewResult((Array.reduce(hunk.lines, { rows: [...rows, ...gapRows], numbers: { old: hunk.oldStart, new: hunk.newStart } }, (state, line) =>
|
|
46
|
+
// "" starts with a "\", a prefix that draws no row
|
|
47
|
+
__foldkitBrandViewResult((Option.match(kindOf(line), {
|
|
48
|
+
onNone: () => __foldkitBrandViewResult((state), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#onNone"),
|
|
49
|
+
onSome: (kind) => {
|
|
50
|
+
const [nextNumbers, row] = numberLine(state.numbers, kind, line.slice(1));
|
|
51
|
+
return __foldkitBrandViewResult(({ rows: [...state.rows, row], numbers: nextNumbers }), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#onSome");
|
|
52
|
+
},
|
|
53
|
+
})), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~13"))), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#readHunk");
|
|
54
|
+
};
|
|
55
|
+
const onlyHunksOf = (files) => __foldkitBrandViewResult((Array.match(files, {
|
|
56
|
+
onEmpty: () => __foldkitBrandViewResult((Option.none()), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#onEmpty"),
|
|
57
|
+
onNonEmpty: ([file, ...others]) => __foldkitBrandViewResult((Array.isArrayEmpty(others) && Array.isArrayNonEmpty(file.hunks)
|
|
58
|
+
? Option.some(file.hunks)
|
|
59
|
+
: Option.none()), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#onNonEmpty"),
|
|
60
|
+
})), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#onlyHunksOf");
|
|
61
|
+
/** The rows of one file's unified diff, numbered from its hunks' headers, with the unchanged
|
|
62
|
+
* lines between hunks folded into a count. `None` unless it is one file with a hunk: text that
|
|
63
|
+
* is not a diff, a binary change and several files have no lines to show. */
|
|
64
|
+
export const diffRowsOfPatch = (patch) => __foldkitBrandViewResult((pipe(Option.liftThrowable(parsePatch)(patch.replaceAll("\r\n", "\n")), Option.flatMap(onlyHunksOf), Option.map((hunks) => __foldkitBrandViewResult((Array.reduce(hunks, { rows: [], numbers: { old: 1, new: 1 } }, readHunk).rows), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~14")))), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#diffRowsOfPatch");
|
|
44
65
|
// a run of one stays a line: a row that says "1 unchanged line" takes the same room as the line
|
|
45
66
|
const foldRow = (run) => __foldkitBrandViewResult((run.length === 1
|
|
46
|
-
?
|
|
47
|
-
: DiffRow.Fold({ count: run.length,
|
|
67
|
+
? Array.headNonEmpty(run)
|
|
68
|
+
: DiffRow.Fold({ count: run.length, firstOldNumber: Array.headNonEmpty(run).oldNumber })), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#foldRow");
|
|
48
69
|
const flushFold = ({ rows, folded }) => __foldkitBrandViewResult((Array.match(folded, {
|
|
49
|
-
onEmpty: () => __foldkitBrandViewResult((rows), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#onEmpty"),
|
|
50
|
-
onNonEmpty: (run) => __foldkitBrandViewResult(([...rows, foldRow(run)]), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#onNonEmpty"),
|
|
70
|
+
onEmpty: () => __foldkitBrandViewResult((rows), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#onEmpty~2"),
|
|
71
|
+
onNonEmpty: (run) => __foldkitBrandViewResult(([...rows, foldRow(run)]), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#onNonEmpty~2"),
|
|
51
72
|
})), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#flushFold");
|
|
52
73
|
/** The diff's rows: every change with `context` unchanged lines around it, and each longer
|
|
53
74
|
* run of unchanged lines folded into one row that counts them (a run of one stays a line). */
|
|
54
|
-
export const foldUnchanged = (
|
|
55
|
-
const isKept = (index) => __foldkitBrandViewResult((hasChanges(
|
|
56
|
-
return __foldkitBrandViewResult((flushFold(Array.reduce(
|
|
57
|
-
? { rows:
|
|
58
|
-
: { rows: state
|
|
75
|
+
export const foldUnchanged = (rows, context) => {
|
|
76
|
+
const isKept = (index) => __foldkitBrandViewResult((hasChanges(rows.slice(Math.max(0, index - context), index + context + 1))), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#isKept");
|
|
77
|
+
return __foldkitBrandViewResult((flushFold(Array.reduce(rows, { rows: [], folded: [] }, (state, row, index) => __foldkitBrandViewResult((row._tag === "Same" && !isKept(index)
|
|
78
|
+
? { rows: state.rows, folded: [...state.folded, row] }
|
|
79
|
+
: { rows: [...flushFold(state), row], folded: [] }), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#anonymous~15")))), "@gjermundgaraba-designs/pixel-paper/src/lineDiff/diff.js#foldUnchanged");
|
|
59
80
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { view } from "./lineDiff.ts";
|
|
2
|
-
export type { ViewConfig } from "./lineDiff.ts";
|
|
1
|
+
export { patchView, view } from "./lineDiff.ts";
|
|
2
|
+
export type { PatchViewConfig, ViewConfig } from "./lineDiff.ts";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lineDiff/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lineDiff/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { view } from "./lineDiff.js";
|
|
1
|
+
export { patchView, view } from "./lineDiff.js";
|