@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
|
@@ -24,14 +24,16 @@ export type ViewConfig<Message> = Readonly<{
|
|
|
24
24
|
options: ReadonlyArray<SelectOption | SelectGroup>;
|
|
25
25
|
/** A sans sentence under the control. */
|
|
26
26
|
help?: string;
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/** Why the value was refused: a red sentence under the control, which the control is
|
|
28
|
+
* described by, with red on its box and its label (`aria-invalid`). Say it again in an
|
|
29
|
+
* ErrorSummary above the form. */
|
|
30
|
+
error?: string | undefined;
|
|
29
31
|
/** The form is not sent without it: screen readers hear "required" (`aria-required`). */
|
|
30
32
|
isRequired?: boolean;
|
|
31
33
|
}>;
|
|
32
34
|
/** A labelled select with its help line, built on `@foldkit/ui`'s `Select`: the label names
|
|
33
|
-
* the select, the help line describes it (`aria-describedby`), `
|
|
34
|
-
* `aria-invalid` and `isRequired` sets `aria-required`.
|
|
35
|
-
* caret and the picker. */
|
|
35
|
+
* the select, 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 stylesheet draws the box, the caret and the picker. */
|
|
36
38
|
export declare const view: <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Message>) => Html;
|
|
37
39
|
//# sourceMappingURL=selectField.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selectField.d.ts","sourceRoot":"","sources":["../../../src/selectField/selectField.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"selectField.d.ts","sourceRoot":"","sources":["../../../src/selectField/selectField.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMtD,qCAAqC;AACrC,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC,CAAC;AAEH,mDAAmD;AACnD,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;IACjC,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACtC,CAAC,CAAC;AAEH,8DAA8D;AAC9D,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACrC,sEAAsE;IACtE,OAAO,EAAE,aAAa,CAAC,YAAY,GAAG,WAAW,CAAC,CAAC;IACnD,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;CACtB,CAAC,CAAC;AAoBH;;;8DAG8D;AAC9D,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAiCpF,CAAC"}
|
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
|
|
2
2
|
import { Select as UiSelect } from "@foldkit/ui";
|
|
3
3
|
import { Array } from "effect";
|
|
4
|
+
import { describedByError, errorView } from "../field/field.js";
|
|
4
5
|
const isGroup = (entry) => __foldkitBrandViewResult(("options" in entry), "@gjermundgaraba-designs/pixel-paper/src/selectField/selectField.js#isGroup");
|
|
5
6
|
const optionView = (h) => __foldkitBrandViewResult((({ value, label, isDisabled = false }) => __foldkitBrandViewResult((h.keyed("option")(value, [h.Value(value), h.Disabled(isDisabled)], [label])), "@gjermundgaraba-designs/pixel-paper/src/selectField/selectField.js#anonymous")), "@gjermundgaraba-designs/pixel-paper/src/selectField/selectField.js#optionView");
|
|
6
7
|
const entryView = (h) => __foldkitBrandViewResult(((entry) => __foldkitBrandViewResult((isGroup(entry)
|
|
7
8
|
? h.keyed("optgroup")(entry.key, [h.Attribute("label", entry.label)], Array.map(entry.options, optionView(h)))
|
|
8
9
|
: optionView(h)(entry)), "@gjermundgaraba-designs/pixel-paper/src/selectField/selectField.js#anonymous~2")), "@gjermundgaraba-designs/pixel-paper/src/selectField/selectField.js#entryView");
|
|
9
10
|
/** A labelled select with its help line, built on `@foldkit/ui`'s `Select`: the label names
|
|
10
|
-
* the select, the help line describes it (`aria-describedby`), `
|
|
11
|
-
* `aria-invalid` and `isRequired` sets `aria-required`.
|
|
12
|
-
* caret and the picker. */
|
|
11
|
+
* the select, the help line describes it (`aria-describedby`), an `error` sets
|
|
12
|
+
* `aria-invalid` and describes it before the help, and `isRequired` sets `aria-required`.
|
|
13
|
+
* The stylesheet draws the box, the caret and the picker. */
|
|
13
14
|
export const view = (config, h) => {
|
|
14
|
-
const { id, label, value, onChange, options, help,
|
|
15
|
+
const { id, label, value, onChange, options, help, error, isRequired = false } = config;
|
|
15
16
|
return __foldkitBrandViewResult((UiSelect.view({
|
|
16
17
|
id,
|
|
17
18
|
value,
|
|
18
19
|
onChange,
|
|
19
|
-
isInvalid,
|
|
20
|
+
isInvalid: error !== undefined,
|
|
20
21
|
hasDescription: help !== undefined,
|
|
21
22
|
toView: (groups) => __foldkitBrandViewResult((h.div([h.Class("field")], [
|
|
22
23
|
h.label(groups.label, [label]),
|
|
23
24
|
// NOTE: @foldkit/ui gap: Select has no required option, so the wrapper says it.
|
|
24
|
-
h.select([
|
|
25
|
+
h.select([
|
|
26
|
+
...groups.select,
|
|
27
|
+
...describedByError(id, error, help !== undefined, h),
|
|
28
|
+
...(isRequired ? [h.AriaRequired(true)] : []),
|
|
29
|
+
], Array.map(options, entryView(h))),
|
|
30
|
+
...errorView(id, error, h),
|
|
25
31
|
...(help === undefined
|
|
26
32
|
? []
|
|
27
33
|
: [h.p([...groups.description, h.Class("field-help")], [help])]),
|
|
@@ -10,7 +10,8 @@ export type ViewConfig = Readonly<{
|
|
|
10
10
|
/** A Prose, and anything under it. */
|
|
11
11
|
body: ReadonlyArray<Html | string>;
|
|
12
12
|
}>;
|
|
13
|
-
/** An authored page
|
|
14
|
-
* system slows to reading pace.
|
|
13
|
+
/** An authored page, an `<article>` under its `h1` on its own sheet of paper, framed as a
|
|
14
|
+
* panel is: the one place the system slows to reading pace. It stands in a panel's place,
|
|
15
|
+
* never inside one, so its `h1` opens the page instead of following a panel's `h2`. */
|
|
15
16
|
export declare const view: <Message>(config: ViewConfig, h: HtmlBuilder<Message>) => Html;
|
|
16
17
|
//# sourceMappingURL=sheet.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sheet.d.ts","sourceRoot":"","sources":["../../../src/sheet/sheet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMtD,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACtC,mDAAmD;IACnD,OAAO,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACvC,sCAAsC;IACtC,IAAI,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;CACpC,CAAC,CAAC;AAEH;
|
|
1
|
+
{"version":3,"file":"sheet.d.ts","sourceRoot":"","sources":["../../../src/sheet/sheet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMtD,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACtC,mDAAmD;IACnD,OAAO,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACvC,sCAAsC;IACtC,IAAI,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;CACpC,CAAC,CAAC;AAEH;;wFAEwF;AACxF,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAc3E,CAAC"}
|
package/dist/src/sheet/sheet.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
|
|
2
2
|
import { slotView } from "../slot/slot.js";
|
|
3
|
-
/** An authored page
|
|
4
|
-
* system slows to reading pace.
|
|
3
|
+
/** An authored page, an `<article>` under its `h1` on its own sheet of paper, framed as a
|
|
4
|
+
* panel is: the one place the system slows to reading pace. It stands in a panel's place,
|
|
5
|
+
* never inside one, so its `h1` opens the page instead of following a panel's `h2`. */
|
|
5
6
|
export const view = (config, h) => {
|
|
6
7
|
const { title, byline = [], actions = [], body } = config;
|
|
7
8
|
return __foldkitBrandViewResult((h.article([h.Class("sheet")], [
|
package/dist/src/slot/slot.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
2
|
+
import type { Attributes } from "../attributes/attributes.ts";
|
|
2
3
|
/** The elements a slot is wrapped in. */
|
|
3
4
|
type SlotTag = "div" | "span" | "p" | "dd" | "ul";
|
|
4
5
|
/** An optional slot in its one element, `tag.slotClass`, to spread among its siblings: nothing
|
|
5
6
|
* while the slot is empty, so an unused slot leaves no box, gap or rule behind. */
|
|
6
|
-
export declare const slotView: <Message>(tag: SlotTag, slotClass: string, slot: ReadonlyArray<Html | string>, h: HtmlBuilder<Message>) => ReadonlyArray<Html>;
|
|
7
|
+
export declare const slotView: <Message>(tag: SlotTag, slotClass: string, slot: ReadonlyArray<Html | string>, h: HtmlBuilder<Message>, attributes?: Attributes<Message>) => ReadonlyArray<Html>;
|
|
7
8
|
export {};
|
|
8
9
|
//# sourceMappingURL=slot.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slot.d.ts","sourceRoot":"","sources":["../../../src/slot/slot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEtD,yCAAyC;AACzC,KAAK,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD;oFACoF;AACpF,eAAO,MAAM,QAAQ,GAAI,OAAO,OACzB,OAAO,aACD,MAAM,QACX,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,KAC/B,WAAW,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"slot.d.ts","sourceRoot":"","sources":["../../../src/slot/slot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,yCAAyC;AACzC,KAAK,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD;oFACoF;AACpF,eAAO,MAAM,QAAQ,GAAI,OAAO,OACzB,OAAO,aACD,MAAM,QACX,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,KAC/B,WAAW,CAAC,OAAO,CAAC,eACX,UAAU,CAAC,OAAO,CAAC,KAC9B,aAAa,CAAC,IAAI,CAIjB,CAAC"}
|
package/dist/src/slot/slot.js
CHANGED
|
@@ -2,7 +2,7 @@ import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
|
|
|
2
2
|
import { Array } from "effect";
|
|
3
3
|
/** An optional slot in its one element, `tag.slotClass`, to spread among its siblings: nothing
|
|
4
4
|
* while the slot is empty, so an unused slot leaves no box, gap or rule behind. */
|
|
5
|
-
export const slotView = (tag, slotClass, slot, h) => __foldkitBrandViewResult((Array.match(slot, {
|
|
5
|
+
export const slotView = (tag, slotClass, slot, h, attributes = []) => __foldkitBrandViewResult((Array.match(slot, {
|
|
6
6
|
onEmpty: () => __foldkitBrandViewResult(([]), "@gjermundgaraba-designs/pixel-paper/src/slot/slot.js#onEmpty"),
|
|
7
|
-
onNonEmpty: (content) => __foldkitBrandViewResult(([h[tag]([h.Class(slotClass)], content)]), "@gjermundgaraba-designs/pixel-paper/src/slot/slot.js#onNonEmpty"),
|
|
7
|
+
onNonEmpty: (content) => __foldkitBrandViewResult(([h[tag]([...attributes, h.Class(slotClass)], content)]), "@gjermundgaraba-designs/pixel-paper/src/slot/slot.js#onNonEmpty"),
|
|
8
8
|
})), "@gjermundgaraba-designs/pixel-paper/src/slot/slot.js#slotView");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
1
|
+
import type { ChildAttribute, Html, HtmlBuilder, TextareaAttribute } from "foldkit/html";
|
|
2
2
|
/** Configuration for rendering a textarea field with `view`. */
|
|
3
3
|
export type ViewConfig<Message> = Readonly<{
|
|
4
4
|
id: string;
|
|
@@ -7,13 +7,23 @@ export type ViewConfig<Message> = Readonly<{
|
|
|
7
7
|
onInput: (value: string) => Message;
|
|
8
8
|
/** A sans sentence under the control. */
|
|
9
9
|
help?: string;
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/** Why the value was refused: a red sentence under the control, which the control is
|
|
11
|
+
* described by, with red on its rule and its label (`aria-invalid`). Say it again in an
|
|
12
|
+
* ErrorSummary above the form. */
|
|
13
|
+
error?: string | undefined;
|
|
14
|
+
/** The form is not sent without it: screen readers hear "required" (`aria-required`). */
|
|
15
|
+
isRequired?: boolean;
|
|
16
|
+
/** Code and identifiers: the system monospace. */
|
|
17
|
+
isMono?: boolean;
|
|
12
18
|
/** The lines it opens at; it grows with its text from there. */
|
|
13
19
|
rows?: number;
|
|
20
|
+
/** Further attributes for the `<textarea>`, such as a placeholder or spelling off; never a
|
|
21
|
+
* class, and never its text, which is `value`. */
|
|
22
|
+
textareaAttributes?: ReadonlyArray<TextareaAttribute<Message> | ChildAttribute>;
|
|
14
23
|
}>;
|
|
15
24
|
/** A labelled textarea with its help line, built on `@foldkit/ui`'s `Textarea`: the label
|
|
16
|
-
* names the textarea, the help line describes it (`aria-describedby`)
|
|
17
|
-
* `aria-invalid
|
|
25
|
+
* names the textarea, the help line describes it (`aria-describedby`), an `error` sets
|
|
26
|
+
* `aria-invalid` and describes it before the help, and `isRequired` sets `aria-required`.
|
|
27
|
+
* Boxless like a Field, on a dotted baseline, with an ink corner grip. */
|
|
18
28
|
export declare const view: <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Message>) => Html;
|
|
19
29
|
//# sourceMappingURL=textareaField.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textareaField.d.ts","sourceRoot":"","sources":["../../../src/textareaField/textareaField.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"textareaField.d.ts","sourceRoot":"","sources":["../../../src/textareaField/textareaField.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAMzF,gEAAgE;AAChE,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,kDAAkD;IAClD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;uDACmD;IACnD,kBAAkB,CAAC,EAAE,aAAa,CAAC,iBAAiB,CAAC,OAAO,CAAC,GAAG,cAAc,CAAC,CAAC;CACjF,CAAC,CAAC;AAEH;;;2EAG2E;AAC3E,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IA2CpF,CAAC"}
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
|
|
2
2
|
import { Textarea as UiTextarea } from "@foldkit/ui";
|
|
3
|
+
import { describedByError, errorView } from "../field/field.js";
|
|
3
4
|
/** A labelled textarea with its help line, built on `@foldkit/ui`'s `Textarea`: the label
|
|
4
|
-
* names the textarea, the help line describes it (`aria-describedby`)
|
|
5
|
-
* `aria-invalid
|
|
5
|
+
* names the textarea, the help line describes it (`aria-describedby`), an `error` sets
|
|
6
|
+
* `aria-invalid` and describes it before the help, and `isRequired` sets `aria-required`.
|
|
7
|
+
* Boxless like a Field, on a dotted baseline, with an ink corner grip. */
|
|
6
8
|
export const view = (config, h) => {
|
|
7
|
-
const { id, label, value, onInput, help,
|
|
9
|
+
const { id, label, value, onInput, help, error, isRequired = false, isMono = false, rows, textareaAttributes = [], } = config;
|
|
8
10
|
return __foldkitBrandViewResult((UiTextarea.view({
|
|
9
11
|
id,
|
|
10
12
|
value,
|
|
11
13
|
onInput,
|
|
12
|
-
isInvalid,
|
|
14
|
+
isInvalid: error !== undefined,
|
|
13
15
|
hasDescription: help !== undefined,
|
|
14
16
|
...(rows !== undefined && { rows }),
|
|
15
|
-
toView: (groups) => __foldkitBrandViewResult((h.div([h.Class(
|
|
17
|
+
toView: (groups) => __foldkitBrandViewResult((h.div([h.Class(`field${isMono ? " mono" : ""}`)], [
|
|
16
18
|
h.label(groups.label, [label]),
|
|
17
|
-
|
|
19
|
+
// NOTE: @foldkit/ui gap: Textarea has no required option, so the wrapper says it.
|
|
20
|
+
h.textarea([
|
|
21
|
+
...textareaAttributes,
|
|
22
|
+
...groups.textarea,
|
|
23
|
+
...describedByError(id, error, help !== undefined, h),
|
|
24
|
+
...(isRequired ? [h.AriaRequired(true)] : []),
|
|
25
|
+
]),
|
|
26
|
+
...errorView(id, error, h),
|
|
18
27
|
...(help === undefined
|
|
19
28
|
? []
|
|
20
29
|
: [h.p([...groups.description, h.Class("field-help")], [help])]),
|
package/package.json
CHANGED
|
@@ -7,6 +7,10 @@ import type { Html, HtmlBuilder } from "foldkit/html";
|
|
|
7
7
|
export type ViewConfig = Readonly<{
|
|
8
8
|
/** Pixel caps: what is missing (`"Nothing queued"`). */
|
|
9
9
|
title: string;
|
|
10
|
+
/** Makes the title a heading at this level, for an empty state that is the page's or the
|
|
11
|
+
* section's main content (a gate, a missing page); a `strong` when not given. It looks the
|
|
12
|
+
* same either way. */
|
|
13
|
+
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
10
14
|
/** One sentence: how it gets filled. */
|
|
11
15
|
body?: ReadonlyArray<Html | string>;
|
|
12
16
|
/** What fills it: a button under the sentence. */
|
|
@@ -15,12 +19,14 @@ export type ViewConfig = Readonly<{
|
|
|
15
19
|
|
|
16
20
|
/** What a panel says when it has nothing to show, centred where its content would be. */
|
|
17
21
|
export const view = <Message>(config: ViewConfig, h: HtmlBuilder<Message>): Html => {
|
|
18
|
-
const { title, body = [], actions = [] } = config;
|
|
22
|
+
const { title, headingLevel, body = [], actions = [] } = config;
|
|
19
23
|
|
|
20
24
|
return h.div(
|
|
21
25
|
[h.Class("empty")],
|
|
22
26
|
[
|
|
23
|
-
|
|
27
|
+
headingLevel === undefined
|
|
28
|
+
? h.strong([h.Class("empty-title")], [title])
|
|
29
|
+
: h[`h${headingLevel}`]([h.Class("empty-title")], [title]),
|
|
24
30
|
...Array.match(body, { onEmpty: () => [], onNonEmpty: (sentence) => [h.p([], sentence)] }),
|
|
25
31
|
...actions,
|
|
26
32
|
],
|
package/src/field/field.ts
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
import { Input as UiInput } from "@foldkit/ui";
|
|
2
|
-
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
2
|
+
import type { Html, HtmlBuilder, TextareaAttribute } from "foldkit/html";
|
|
3
3
|
|
|
4
4
|
import type { Attributes } from "../attributes/attributes.ts";
|
|
5
5
|
|
|
6
|
+
// ERROR
|
|
7
|
+
|
|
8
|
+
const errorId = (id: string): string => `${id}-error`;
|
|
9
|
+
|
|
10
|
+
/** The red sentence under a field's control that says why it failed, given an error. */
|
|
11
|
+
export const errorView = <Message>(
|
|
12
|
+
id: string,
|
|
13
|
+
error: string | undefined,
|
|
14
|
+
h: HtmlBuilder<Message>,
|
|
15
|
+
): ReadonlyArray<Html> =>
|
|
16
|
+
error === undefined ? [] : [h.p([h.Id(errorId(id)), h.Class("field-error")], [error])];
|
|
17
|
+
|
|
18
|
+
/** Points the control at its error, then at its help line when it has one. After the
|
|
19
|
+
* `@foldkit/ui` attributes, whose `aria-describedby` names the help line alone; every
|
|
20
|
+
* `@foldkit/ui` control names that line `<id>-description`, as Input does. */
|
|
21
|
+
export const describedByError = <Message>(
|
|
22
|
+
id: string,
|
|
23
|
+
error: string | undefined,
|
|
24
|
+
hasHelp: boolean,
|
|
25
|
+
h: HtmlBuilder<Message>,
|
|
26
|
+
): ReadonlyArray<TextareaAttribute<Message>> =>
|
|
27
|
+
error === undefined
|
|
28
|
+
? []
|
|
29
|
+
: [h.AriaDescribedBy(hasHelp ? `${errorId(id)} ${UiInput.descriptionId(id)}` : errorId(id))];
|
|
30
|
+
|
|
6
31
|
// VIEW
|
|
7
32
|
|
|
8
33
|
/** Configuration for rendering a text field with `view`. */
|
|
@@ -13,8 +38,10 @@ export type ViewConfig<Message> = Readonly<{
|
|
|
13
38
|
onInput: (value: string) => Message;
|
|
14
39
|
/** A sans sentence under the control. */
|
|
15
40
|
help?: string;
|
|
16
|
-
/**
|
|
17
|
-
|
|
41
|
+
/** Why the value was refused: a red sentence under the control, which the control is
|
|
42
|
+
* described by, with red on its rule and its label (`aria-invalid`). Say it again in an
|
|
43
|
+
* ErrorSummary above the form. */
|
|
44
|
+
error?: string | undefined;
|
|
18
45
|
/** The form is not sent without it: screen readers hear "required" (`aria-required`). */
|
|
19
46
|
isRequired?: boolean;
|
|
20
47
|
/** A 2px ink box: the field that is the whole job of its form. A date is always boxed. */
|
|
@@ -30,9 +57,9 @@ export type ViewConfig<Message> = Readonly<{
|
|
|
30
57
|
}>;
|
|
31
58
|
|
|
32
59
|
/** A labelled text input with its help line, built on `@foldkit/ui`'s `Input`: the label
|
|
33
|
-
* names the input, the help line describes it (`aria-describedby`), `
|
|
34
|
-
* `aria-invalid` and `isRequired` sets `aria-required`.
|
|
35
|
-
* baseline that turns solid ink on focus. */
|
|
60
|
+
* names the input, the help line describes it (`aria-describedby`), an `error` sets
|
|
61
|
+
* `aria-invalid` and describes it before the help, and `isRequired` sets `aria-required`.
|
|
62
|
+
* The input is boxless, a dotted baseline that turns solid ink on focus. */
|
|
36
63
|
export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Message>): Html => {
|
|
37
64
|
const {
|
|
38
65
|
id,
|
|
@@ -40,7 +67,7 @@ export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Messag
|
|
|
40
67
|
value,
|
|
41
68
|
onInput,
|
|
42
69
|
help,
|
|
43
|
-
|
|
70
|
+
error,
|
|
44
71
|
isRequired = false,
|
|
45
72
|
isBoxed = false,
|
|
46
73
|
isMono = false,
|
|
@@ -53,7 +80,7 @@ export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Messag
|
|
|
53
80
|
id,
|
|
54
81
|
value,
|
|
55
82
|
onInput,
|
|
56
|
-
isInvalid,
|
|
83
|
+
isInvalid: error !== undefined,
|
|
57
84
|
...(type !== undefined && { type }),
|
|
58
85
|
hasDescription: help !== undefined,
|
|
59
86
|
toView: (groups) =>
|
|
@@ -65,8 +92,10 @@ export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Messag
|
|
|
65
92
|
h.input([
|
|
66
93
|
...inputAttributes,
|
|
67
94
|
...groups.input,
|
|
95
|
+
...describedByError(id, error, help !== undefined, h),
|
|
68
96
|
...(isRequired ? [h.AriaRequired(true)] : []),
|
|
69
97
|
]),
|
|
98
|
+
...errorView(id, error, h),
|
|
70
99
|
...(help === undefined
|
|
71
100
|
? []
|
|
72
101
|
: [h.p([...groups.description, h.Class("field-help")], [help])]),
|
package/src/fieldset/fieldset.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Fieldset as UiFieldset } from "@foldkit/ui";
|
|
2
2
|
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
3
3
|
|
|
4
|
+
import { describedByError, errorView } from "../field/field.ts";
|
|
5
|
+
|
|
4
6
|
// VIEW
|
|
5
7
|
|
|
6
8
|
/** Configuration for rendering a fieldset with `view`. */
|
|
@@ -11,15 +13,20 @@ export type ViewConfig = Readonly<{
|
|
|
11
13
|
label: string;
|
|
12
14
|
/** A sans sentence under the controls; it describes the group. */
|
|
13
15
|
help?: string;
|
|
16
|
+
/** Why the choice was refused: a red sentence under the controls, which the group is
|
|
17
|
+
* described by, before its help. Mark the control itself invalid too (RadioChoices'
|
|
18
|
+
* `isInvalid`), which turns the legend red. Say it again in an ErrorSummary above the
|
|
19
|
+
* form. */
|
|
20
|
+
error?: string | undefined;
|
|
14
21
|
/** The controls: Choices, a Segmented. */
|
|
15
22
|
body: ReadonlyArray<Html | string>;
|
|
16
23
|
}>;
|
|
17
24
|
|
|
18
25
|
/** A group of controls under one name, built on `@foldkit/ui`'s Fieldset: a `<fieldset>`
|
|
19
|
-
* named by its `<legend>` and described by its
|
|
20
|
-
* turns red while a control in it is invalid. */
|
|
26
|
+
* named by its `<legend>` and described by its error and its help line
|
|
27
|
+
* (`aria-describedby`). The legend turns red while a control in it is invalid. */
|
|
21
28
|
export const view = <Message>(config: ViewConfig, h: HtmlBuilder<Message>): Html => {
|
|
22
|
-
const { id, label, help, body } = config;
|
|
29
|
+
const { id, label, help, error, body } = config;
|
|
23
30
|
|
|
24
31
|
return UiFieldset.view(
|
|
25
32
|
{
|
|
@@ -27,10 +34,15 @@ export const view = <Message>(config: ViewConfig, h: HtmlBuilder<Message>): Html
|
|
|
27
34
|
hasDescription: help !== undefined,
|
|
28
35
|
toView: (groups) =>
|
|
29
36
|
h.fieldset(
|
|
30
|
-
[
|
|
37
|
+
[
|
|
38
|
+
...groups.fieldset,
|
|
39
|
+
...describedByError(id, error, help !== undefined, h),
|
|
40
|
+
h.Class("field"),
|
|
41
|
+
],
|
|
31
42
|
[
|
|
32
43
|
h.legend([...groups.legend, h.Class("field-label")], [label]),
|
|
33
44
|
...body,
|
|
45
|
+
...errorView(id, error, h),
|
|
34
46
|
...(help === undefined
|
|
35
47
|
? []
|
|
36
48
|
: [h.p([...groups.description, h.Class("field-help")], [help])]),
|
package/src/lineDiff/diff.ts
CHANGED
|
@@ -1,30 +1,24 @@
|
|
|
1
|
-
import { diffLines } from "diff";
|
|
1
|
+
import { type StructuredPatchHunk, diffLines, parsePatch } from "diff";
|
|
2
2
|
import { Array, Match, Option, Schema, pipe } from "effect";
|
|
3
3
|
import { defineTaggedUnion } from "foldkit/schema";
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// ROWS
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
export type LineKind = typeof LineKind.Type;
|
|
10
|
-
|
|
11
|
-
/** One line of a diff, numbered on the side or sides it is on. */
|
|
12
|
-
export const DiffLine = Schema.Struct({
|
|
13
|
-
kind: LineKind,
|
|
14
|
-
text: Schema.String,
|
|
15
|
-
maybeOldNumber: Schema.Option(Schema.Number),
|
|
16
|
-
maybeNewNumber: Schema.Option(Schema.Number),
|
|
17
|
-
});
|
|
18
|
-
export type DiffLine = typeof DiffLine.Type;
|
|
19
|
-
|
|
20
|
-
/** A row of a folded diff: a line, or a run of two or more unchanged lines folded into a
|
|
21
|
-
* count, with the run's first line. */
|
|
7
|
+
/** A row of a diff: a line, numbered on the side or sides it is on, or a run of unchanged
|
|
8
|
+
* lines folded into a count, known by its first line's old number. */
|
|
22
9
|
export const DiffRow = defineTaggedUnion({
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
Same: { text: Schema.String, oldNumber: Schema.Number, newNumber: Schema.Number },
|
|
11
|
+
Added: { text: Schema.String, newNumber: Schema.Number },
|
|
12
|
+
Removed: { text: Schema.String, oldNumber: Schema.Number },
|
|
13
|
+
Fold: { count: Schema.Number, firstOldNumber: Schema.Number },
|
|
25
14
|
});
|
|
26
15
|
export type DiffRow = typeof DiffRow.Type;
|
|
27
16
|
|
|
17
|
+
type SameRow = Extract<DiffRow, Readonly<{ _tag: "Same" }>>;
|
|
18
|
+
|
|
19
|
+
/** What a line did between the two sides. */
|
|
20
|
+
export type LineKind = "Same" | "Added" | "Removed";
|
|
21
|
+
|
|
28
22
|
type Numbers = Readonly<{ old: number; new: number }>;
|
|
29
23
|
|
|
30
24
|
const withFinalNewline = (text: string): string => (text.endsWith("\n") ? text : `${text}\n`);
|
|
@@ -37,24 +31,27 @@ const changeKind = (change: Readonly<{ added: boolean; removed: boolean }>): Lin
|
|
|
37
31
|
Match.orElse(() => "Same"),
|
|
38
32
|
);
|
|
39
33
|
|
|
40
|
-
const numberLine = (numbers: Numbers, kind: LineKind, text: string): [Numbers,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
34
|
+
const numberLine = (numbers: Numbers, kind: LineKind, text: string): [Numbers, DiffRow] =>
|
|
35
|
+
Match.value(kind).pipe(
|
|
36
|
+
Match.withReturnType<[Numbers, DiffRow]>(),
|
|
37
|
+
Match.when("Same", () => [
|
|
38
|
+
{ old: numbers.old + 1, new: numbers.new + 1 },
|
|
39
|
+
DiffRow.Same({ text, oldNumber: numbers.old, newNumber: numbers.new }),
|
|
40
|
+
]),
|
|
41
|
+
Match.when("Added", () => [
|
|
42
|
+
{ old: numbers.old, new: numbers.new + 1 },
|
|
43
|
+
DiffRow.Added({ text, newNumber: numbers.new }),
|
|
44
|
+
]),
|
|
45
|
+
Match.when("Removed", () => [
|
|
46
|
+
{ old: numbers.old + 1, new: numbers.new },
|
|
47
|
+
DiffRow.Removed({ text, oldNumber: numbers.old }),
|
|
48
|
+
]),
|
|
49
|
+
Match.exhaustive,
|
|
50
|
+
);
|
|
54
51
|
|
|
55
52
|
/** The line diff of two texts (Myers, through jsdiff), each line numbered on its side. A
|
|
56
53
|
* missing final newline is not a change. */
|
|
57
|
-
export const
|
|
54
|
+
export const diffRowsOf = (before: string, after: string): ReadonlyArray<DiffRow> =>
|
|
58
55
|
pipe(
|
|
59
56
|
diffLines(withFinalNewline(before), withFinalNewline(after)),
|
|
60
57
|
Array.flatMap((change) =>
|
|
@@ -67,23 +64,82 @@ export const diffLinesOf = (before: string, after: string): ReadonlyArray<DiffLi
|
|
|
67
64
|
Array.mapAccum(lines, { old: 1, new: 1 }, (numbers, { kind, text }) =>
|
|
68
65
|
numberLine(numbers, kind, text),
|
|
69
66
|
),
|
|
70
|
-
([,
|
|
67
|
+
([, rows]) => rows,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
/** Whether a line was added or removed. */
|
|
71
|
+
export const hasChanges = (rows: ReadonlyArray<DiffRow>): boolean =>
|
|
72
|
+
Array.some(rows, (row) => row._tag === "Added" || row._tag === "Removed");
|
|
73
|
+
|
|
74
|
+
// PATCH
|
|
75
|
+
|
|
76
|
+
type PatchState = Readonly<{ rows: ReadonlyArray<DiffRow>; numbers: Numbers }>;
|
|
77
|
+
|
|
78
|
+
const LINE_KIND_BY_PREFIX: Readonly<Record<string, LineKind>> = {
|
|
79
|
+
" ": "Same",
|
|
80
|
+
"+": "Added",
|
|
81
|
+
"-": "Removed",
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// a blank context line that lost its leading space on the way (a trimming editor, a mail) is
|
|
85
|
+
// still a context line
|
|
86
|
+
const kindOf = (line: string): Option.Option<LineKind> =>
|
|
87
|
+
line === "" ? Option.some("Same") : Option.fromUndefinedOr(LINE_KIND_BY_PREFIX[line.charAt(0)]);
|
|
88
|
+
|
|
89
|
+
const readHunk = ({ rows, numbers }: PatchState, hunk: StructuredPatchHunk): PatchState => {
|
|
90
|
+
const gap = hunk.oldStart - numbers.old;
|
|
91
|
+
const gapRows = gap > 0 ? [DiffRow.Fold({ count: gap, firstOldNumber: numbers.old })] : [];
|
|
92
|
+
|
|
93
|
+
return Array.reduce(
|
|
94
|
+
hunk.lines,
|
|
95
|
+
{ rows: [...rows, ...gapRows], numbers: { old: hunk.oldStart, new: hunk.newStart } },
|
|
96
|
+
(state: PatchState, line): PatchState =>
|
|
97
|
+
// "" starts with a "\", a prefix that draws no row
|
|
98
|
+
Option.match(kindOf(line), {
|
|
99
|
+
onNone: () => state,
|
|
100
|
+
onSome: (kind) => {
|
|
101
|
+
const [nextNumbers, row] = numberLine(state.numbers, kind, line.slice(1));
|
|
102
|
+
return { rows: [...state.rows, row], numbers: nextNumbers };
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const onlyHunksOf = <File extends Readonly<{ hunks: Array<StructuredPatchHunk> }>>(
|
|
109
|
+
files: ReadonlyArray<File>,
|
|
110
|
+
): Option.Option<Array.NonEmptyReadonlyArray<StructuredPatchHunk>> =>
|
|
111
|
+
Array.match(files, {
|
|
112
|
+
onEmpty: () => Option.none(),
|
|
113
|
+
onNonEmpty: ([file, ...others]) =>
|
|
114
|
+
Array.isArrayEmpty(others) && Array.isArrayNonEmpty(file.hunks)
|
|
115
|
+
? Option.some(file.hunks)
|
|
116
|
+
: Option.none(),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
/** The rows of one file's unified diff, numbered from its hunks' headers, with the unchanged
|
|
120
|
+
* lines between hunks folded into a count. `None` unless it is one file with a hunk: text that
|
|
121
|
+
* is not a diff, a binary change and several files have no lines to show. */
|
|
122
|
+
export const diffRowsOfPatch = (patch: string): Option.Option<ReadonlyArray<DiffRow>> =>
|
|
123
|
+
pipe(
|
|
124
|
+
Option.liftThrowable(parsePatch)(patch.replaceAll("\r\n", "\n")),
|
|
125
|
+
Option.flatMap(onlyHunksOf),
|
|
126
|
+
Option.map(
|
|
127
|
+
(hunks) => Array.reduce(hunks, { rows: [], numbers: { old: 1, new: 1 } }, readHunk).rows,
|
|
128
|
+
),
|
|
71
129
|
);
|
|
72
130
|
|
|
73
|
-
|
|
74
|
-
export const hasChanges = (lines: ReadonlyArray<DiffLine>): boolean =>
|
|
75
|
-
Array.some(lines, ({ kind }) => kind !== "Same");
|
|
131
|
+
// FOLD
|
|
76
132
|
|
|
77
133
|
type FoldState = Readonly<{
|
|
78
134
|
rows: ReadonlyArray<DiffRow>;
|
|
79
|
-
folded: ReadonlyArray<
|
|
135
|
+
folded: ReadonlyArray<SameRow>;
|
|
80
136
|
}>;
|
|
81
137
|
|
|
82
138
|
// a run of one stays a line: a row that says "1 unchanged line" takes the same room as the line
|
|
83
|
-
const foldRow = (run: Array.NonEmptyReadonlyArray<
|
|
139
|
+
const foldRow = (run: Array.NonEmptyReadonlyArray<SameRow>): DiffRow =>
|
|
84
140
|
run.length === 1
|
|
85
|
-
?
|
|
86
|
-
: DiffRow.Fold({ count: run.length,
|
|
141
|
+
? Array.headNonEmpty(run)
|
|
142
|
+
: DiffRow.Fold({ count: run.length, firstOldNumber: Array.headNonEmpty(run).oldNumber });
|
|
87
143
|
|
|
88
144
|
const flushFold = ({ rows, folded }: FoldState): ReadonlyArray<DiffRow> =>
|
|
89
145
|
Array.match(folded, {
|
|
@@ -94,17 +150,17 @@ const flushFold = ({ rows, folded }: FoldState): ReadonlyArray<DiffRow> =>
|
|
|
94
150
|
/** The diff's rows: every change with `context` unchanged lines around it, and each longer
|
|
95
151
|
* run of unchanged lines folded into one row that counts them (a run of one stays a line). */
|
|
96
152
|
export const foldUnchanged = (
|
|
97
|
-
|
|
153
|
+
rows: ReadonlyArray<DiffRow>,
|
|
98
154
|
context: number,
|
|
99
155
|
): ReadonlyArray<DiffRow> => {
|
|
100
156
|
const isKept = (index: number): boolean =>
|
|
101
|
-
hasChanges(
|
|
157
|
+
hasChanges(rows.slice(Math.max(0, index - context), index + context + 1));
|
|
102
158
|
|
|
103
159
|
return flushFold(
|
|
104
|
-
Array.reduce(
|
|
105
|
-
isKept(index)
|
|
106
|
-
? { rows:
|
|
107
|
-
: { rows: state
|
|
160
|
+
Array.reduce(rows, { rows: [], folded: [] }, (state: FoldState, row, index): FoldState =>
|
|
161
|
+
row._tag === "Same" && !isKept(index)
|
|
162
|
+
? { rows: state.rows, folded: [...state.folded, row] }
|
|
163
|
+
: { rows: [...flushFold(state), row], folded: [] },
|
|
108
164
|
),
|
|
109
165
|
);
|
|
110
166
|
};
|
package/src/lineDiff/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
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";
|