@gjermundgaraba-designs/pixel-paper 0.11.0 → 0.12.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 +11 -11
- 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/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/rows.d.ts +7 -2
- package/dist/src/rows/rows.d.ts.map +1 -1
- package/dist/src/rows/rows.js +3 -3
- 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 +26 -5
- package/src/radioChoices/radioChoices.ts +2 -2
- package/src/rail/rail.ts +5 -3
- package/src/rows/rows.ts +9 -4
- 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.
|
|
@@ -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.
|
|
@@ -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 `role="group"`, its name and its key handlers (never a class). 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.
|
|
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 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 `role="group"`, its name and its key handlers (never a class). 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";
|
|
@@ -9,8 +9,22 @@ export type ViewConfig = Readonly<{
|
|
|
9
9
|
* default. */
|
|
10
10
|
context?: number;
|
|
11
11
|
}>;
|
|
12
|
+
/** Configuration for rendering a unified diff with `patchView`. */
|
|
13
|
+
export type PatchViewConfig = Readonly<{
|
|
14
|
+
/** One file's unified diff, as `diff -u` or `git diff -- <path>` writes it. */
|
|
15
|
+
patch: string;
|
|
16
|
+
/** Names the two sides for screen readers: `"Version 3 against version 4"`. */
|
|
17
|
+
label: string;
|
|
18
|
+
}>;
|
|
12
19
|
/** A line diff as a ruled table: the old and new numbers, a sign, the line. Added lines sit on
|
|
13
20
|
* the head tint behind an ink +; removed lines go muted behind a red −, and each sign is
|
|
14
21
|
* named for screen readers. Two texts that are the same say so in a sentence. */
|
|
15
22
|
export declare const view: <Message>(config: ViewConfig, h: HtmlBuilder<Message>) => Html;
|
|
23
|
+
/** A unified diff the caller already has (from a server, or `git diff -- <path>`) as `view`'s
|
|
24
|
+
* table: its hunks' lines, numbered from their headers, and the unchanged lines between hunks
|
|
25
|
+
* folded into a counted row. Lines past the last hunk are not in the patch, so nothing counts
|
|
26
|
+
* them. An empty patch says there is no difference, as `view` does; anything but one file
|
|
27
|
+
* with a hunk (text that is not a diff, a binary change, several files) says it cannot be
|
|
28
|
+
* shown as lines. */
|
|
29
|
+
export declare const patchView: <Message>(config: PatchViewConfig, h: HtmlBuilder<Message>) => Html;
|
|
16
30
|
//# sourceMappingURL=lineDiff.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lineDiff.d.ts","sourceRoot":"","sources":["../../../src/lineDiff/lineDiff.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAiBtD,2DAA2D;AAC3D,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;IACd;mBACe;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"lineDiff.d.ts","sourceRoot":"","sources":["../../../src/lineDiff/lineDiff.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAiBtD,2DAA2D;AAC3D,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;IACd;mBACe;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAEH,mEAAmE;AACnE,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AA2EH;;kFAEkF;AAClF,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAO3E,CAAC;AAEF;;;;;sBAKsB;AACtB,eAAO,MAAM,SAAS,GAAI,OAAO,UAAU,eAAe,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAWrF,CAAC"}
|