@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
|
@@ -83,8 +83,8 @@ export type ViewInputs = Readonly<{
|
|
|
83
83
|
options: ReadonlyArray<RadioOption>;
|
|
84
84
|
/** The parent's selection. */
|
|
85
85
|
selectedValue: Option.Option<string>;
|
|
86
|
-
/** The group is `aria-invalid` and its marks and names turn red; say why in
|
|
87
|
-
* ErrorSummary above the form. */
|
|
86
|
+
/** The group is `aria-invalid` and its marks and names turn red; say why in its
|
|
87
|
+
* Fieldset's `error` and in an ErrorSummary above the form. */
|
|
88
88
|
isInvalid?: boolean;
|
|
89
89
|
}>;
|
|
90
90
|
|
package/src/rail/rail.ts
CHANGED
|
@@ -234,8 +234,8 @@ export type ViewInputs = Readonly<{
|
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
236
|
* A column of panels beside the deck, fixed under the top bar and scrolling itself:
|
|
237
|
-
* `div.railed
|
|
238
|
-
* layouts, so nothing in it renders twice or loses its state. At 900px and under it leaves the
|
|
237
|
+
* `div.railed >` the deck, then `aside.rail[popover="auto"][aria-label]`. One element serves
|
|
238
|
+
* both layouts, so nothing in it renders twice or loses its state. At 900px and under it leaves the
|
|
239
239
|
* page, and `span.rail-open > button.btn.small` at the top of the deck opens the same element as a
|
|
240
240
|
* drawer over a veil, its head (the name and a close glyph) showing. Opening focuses its first
|
|
241
241
|
* control; Escape, a press on the veil, the close glyph, focus leaving or the window growing
|
|
@@ -268,9 +268,12 @@ export const view = defineView<Model, Message, ViewInputs>((model, viewInputs, h
|
|
|
268
268
|
],
|
|
269
269
|
);
|
|
270
270
|
|
|
271
|
+
// NOTE: the deck comes first so the page's main content, and its h1, precede the rail's
|
|
272
|
+
// panels in reading order; the stylesheet places the rail, fixed, at the left.
|
|
271
273
|
return h.div(
|
|
272
274
|
[h.Class("railed")],
|
|
273
275
|
[
|
|
276
|
+
toDeckView(openButton),
|
|
274
277
|
h.aside(
|
|
275
278
|
[
|
|
276
279
|
h.Id(model.id),
|
|
@@ -293,7 +296,6 @@ export const view = defineView<Model, Message, ViewInputs>((model, viewInputs, h
|
|
|
293
296
|
...toAsideView(),
|
|
294
297
|
],
|
|
295
298
|
),
|
|
296
|
-
toDeckView(openButton),
|
|
297
299
|
],
|
|
298
300
|
);
|
|
299
301
|
});
|
package/src/rows/rows.ts
CHANGED
|
@@ -55,6 +55,10 @@ export type RowConfig<Message> = Readonly<{
|
|
|
55
55
|
body: ReadonlyArray<Html | string>;
|
|
56
56
|
/** Right-aligned chips, marks, counts, due days. */
|
|
57
57
|
slot?: ReadonlyArray<Html | string>;
|
|
58
|
+
/** Further attributes for the slot's `span.slot`, such as an id the row's line is described
|
|
59
|
+
* by (`aria-describedby`); there is no span, and so nothing to point at, while `slot` is
|
|
60
|
+
* empty. Never a class. */
|
|
61
|
+
slotAttributes?: Attributes<Message>;
|
|
58
62
|
/** Controls that belong to the row's job, before the tools (an approve and reject pair). */
|
|
59
63
|
actions?: ReadonlyArray<Html | string>;
|
|
60
64
|
/** Icon buttons at the row's right end, dimmed until the row is hovered or focused (full
|
|
@@ -95,7 +99,7 @@ const lineView = <Message>(
|
|
|
95
99
|
...box,
|
|
96
100
|
...(config.glyph === undefined ? [] : [config.glyph]),
|
|
97
101
|
text,
|
|
98
|
-
...slotView("span", "slot", config.slot ?? [], h),
|
|
102
|
+
...slotView("span", "slot", config.slot ?? [], h, config.slotAttributes),
|
|
99
103
|
...(config.actions ?? []),
|
|
100
104
|
...slotView("span", "tools", config.tools ?? [], h),
|
|
101
105
|
],
|
|
@@ -162,13 +166,14 @@ export const glyphView = <Message>(config: GlyphConfig, h: HtmlBuilder<Message>)
|
|
|
162
166
|
/** Configuration for rendering a fold toggle with `toggleView`. */
|
|
163
167
|
export type ToggleConfig<Message> = Readonly<{
|
|
164
168
|
isExpanded: boolean;
|
|
165
|
-
/** What
|
|
169
|
+
/** What the nested rows hang under: the toggle is named `"Rows under Notes"` whether open
|
|
170
|
+
* or not, and `aria-expanded` says which. */
|
|
166
171
|
label: string;
|
|
167
172
|
onToggle: Message;
|
|
168
173
|
}>;
|
|
169
174
|
|
|
170
175
|
/** The glyph slot as a fold toggle, built on `@foldkit/ui`'s Button: a ring, open or closed,
|
|
171
|
-
* `aria-expanded`, named for
|
|
176
|
+
* `aria-expanded`, named for the rows it shows. */
|
|
172
177
|
export const toggleView = <Message>(config: ToggleConfig<Message>, h: HtmlBuilder<Message>): Html =>
|
|
173
178
|
UiButton.view(
|
|
174
179
|
{
|
|
@@ -178,7 +183,7 @@ export const toggleView = <Message>(config: ToggleConfig<Message>, h: HtmlBuilde
|
|
|
178
183
|
[
|
|
179
184
|
...groups.button,
|
|
180
185
|
h.AriaExpanded(config.isExpanded),
|
|
181
|
-
h.AriaLabel(
|
|
186
|
+
h.AriaLabel(`Rows under ${config.label}`),
|
|
182
187
|
h.Class("glyph"),
|
|
183
188
|
],
|
|
184
189
|
[Glyph.view({ name: config.isExpanded ? "ring" : "ring-closed" }, h)],
|
|
@@ -2,6 +2,8 @@ import { Select as UiSelect } from "@foldkit/ui";
|
|
|
2
2
|
import { Array } from "effect";
|
|
3
3
|
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
4
4
|
|
|
5
|
+
import { describedByError, errorView } from "../field/field.ts";
|
|
6
|
+
|
|
5
7
|
// VIEW
|
|
6
8
|
|
|
7
9
|
/** One choice in a `SelectField`. */
|
|
@@ -31,8 +33,10 @@ export type ViewConfig<Message> = Readonly<{
|
|
|
31
33
|
options: ReadonlyArray<SelectOption | SelectGroup>;
|
|
32
34
|
/** A sans sentence under the control. */
|
|
33
35
|
help?: string;
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
+
/** Why the value was refused: a red sentence under the control, which the control is
|
|
37
|
+
* described by, with red on its box and its label (`aria-invalid`). Say it again in an
|
|
38
|
+
* ErrorSummary above the form. */
|
|
39
|
+
error?: string | undefined;
|
|
36
40
|
/** The form is not sent without it: screen readers hear "required" (`aria-required`). */
|
|
37
41
|
isRequired?: boolean;
|
|
38
42
|
}>;
|
|
@@ -56,27 +60,18 @@ const entryView =
|
|
|
56
60
|
: optionView(h)(entry);
|
|
57
61
|
|
|
58
62
|
/** A labelled select with its help line, built on `@foldkit/ui`'s `Select`: the label names
|
|
59
|
-
* the select, the help line describes it (`aria-describedby`), `
|
|
60
|
-
* `aria-invalid` and `isRequired` sets `aria-required`.
|
|
61
|
-
* caret and the picker. */
|
|
63
|
+
* the select, the help line describes it (`aria-describedby`), an `error` sets
|
|
64
|
+
* `aria-invalid` and describes it before the help, and `isRequired` sets `aria-required`.
|
|
65
|
+
* The stylesheet draws the box, the caret and the picker. */
|
|
62
66
|
export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Message>): Html => {
|
|
63
|
-
const {
|
|
64
|
-
id,
|
|
65
|
-
label,
|
|
66
|
-
value,
|
|
67
|
-
onChange,
|
|
68
|
-
options,
|
|
69
|
-
help,
|
|
70
|
-
isInvalid = false,
|
|
71
|
-
isRequired = false,
|
|
72
|
-
} = config;
|
|
67
|
+
const { id, label, value, onChange, options, help, error, isRequired = false } = config;
|
|
73
68
|
|
|
74
69
|
return UiSelect.view(
|
|
75
70
|
{
|
|
76
71
|
id,
|
|
77
72
|
value,
|
|
78
73
|
onChange,
|
|
79
|
-
isInvalid,
|
|
74
|
+
isInvalid: error !== undefined,
|
|
80
75
|
hasDescription: help !== undefined,
|
|
81
76
|
toView: (groups) =>
|
|
82
77
|
h.div(
|
|
@@ -85,9 +80,14 @@ export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Messag
|
|
|
85
80
|
h.label(groups.label, [label]),
|
|
86
81
|
// NOTE: @foldkit/ui gap: Select has no required option, so the wrapper says it.
|
|
87
82
|
h.select(
|
|
88
|
-
[
|
|
83
|
+
[
|
|
84
|
+
...groups.select,
|
|
85
|
+
...describedByError(id, error, help !== undefined, h),
|
|
86
|
+
...(isRequired ? [h.AriaRequired(true)] : []),
|
|
87
|
+
],
|
|
89
88
|
Array.map(options, entryView(h)),
|
|
90
89
|
),
|
|
90
|
+
...errorView(id, error, h),
|
|
91
91
|
...(help === undefined
|
|
92
92
|
? []
|
|
93
93
|
: [h.p([...groups.description, h.Class("field-help")], [help])]),
|
package/src/sheet/sheet.ts
CHANGED
|
@@ -16,8 +16,9 @@ export type ViewConfig = Readonly<{
|
|
|
16
16
|
body: ReadonlyArray<Html | string>;
|
|
17
17
|
}>;
|
|
18
18
|
|
|
19
|
-
/** An authored page
|
|
20
|
-
* system slows to reading pace.
|
|
19
|
+
/** An authored page, an `<article>` under its `h1` on its own sheet of paper, framed as a
|
|
20
|
+
* panel is: the one place the system slows to reading pace. It stands in a panel's place,
|
|
21
|
+
* never inside one, so its `h1` opens the page instead of following a panel's `h2`. */
|
|
21
22
|
export const view = <Message>(config: ViewConfig, h: HtmlBuilder<Message>): Html => {
|
|
22
23
|
const { title, byline = [], actions = [], body } = config;
|
|
23
24
|
|
package/src/slot/slot.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Array } from "effect";
|
|
2
2
|
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
3
3
|
|
|
4
|
+
import type { Attributes } from "../attributes/attributes.ts";
|
|
5
|
+
|
|
4
6
|
/** The elements a slot is wrapped in. */
|
|
5
7
|
type SlotTag = "div" | "span" | "p" | "dd" | "ul";
|
|
6
8
|
|
|
@@ -11,8 +13,9 @@ export const slotView = <Message>(
|
|
|
11
13
|
slotClass: string,
|
|
12
14
|
slot: ReadonlyArray<Html | string>,
|
|
13
15
|
h: HtmlBuilder<Message>,
|
|
16
|
+
attributes: Attributes<Message> = [],
|
|
14
17
|
): ReadonlyArray<Html> =>
|
|
15
18
|
Array.match(slot, {
|
|
16
19
|
onEmpty: () => [],
|
|
17
|
-
onNonEmpty: (content) => [h[tag]([h.Class(slotClass)], content)],
|
|
20
|
+
onNonEmpty: (content) => [h[tag]([...attributes, h.Class(slotClass)], content)],
|
|
18
21
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Textarea as UiTextarea } from "@foldkit/ui";
|
|
2
|
-
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
2
|
+
import type { ChildAttribute, Html, HtmlBuilder, TextareaAttribute } from "foldkit/html";
|
|
3
|
+
|
|
4
|
+
import { describedByError, errorView } from "../field/field.ts";
|
|
3
5
|
|
|
4
6
|
// VIEW
|
|
5
7
|
|
|
@@ -11,32 +13,60 @@ export type ViewConfig<Message> = Readonly<{
|
|
|
11
13
|
onInput: (value: string) => Message;
|
|
12
14
|
/** A sans sentence under the control. */
|
|
13
15
|
help?: string;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
+
/** Why the value was refused: a red sentence under the control, which the control is
|
|
17
|
+
* described by, with red on its rule and its label (`aria-invalid`). Say it again in an
|
|
18
|
+
* ErrorSummary above the form. */
|
|
19
|
+
error?: string | undefined;
|
|
20
|
+
/** The form is not sent without it: screen readers hear "required" (`aria-required`). */
|
|
21
|
+
isRequired?: boolean;
|
|
22
|
+
/** Code and identifiers: the system monospace. */
|
|
23
|
+
isMono?: boolean;
|
|
16
24
|
/** The lines it opens at; it grows with its text from there. */
|
|
17
25
|
rows?: number;
|
|
26
|
+
/** Further attributes for the `<textarea>`, such as a placeholder or spelling off; never a
|
|
27
|
+
* class, and never its text, which is `value`. */
|
|
28
|
+
textareaAttributes?: ReadonlyArray<TextareaAttribute<Message> | ChildAttribute>;
|
|
18
29
|
}>;
|
|
19
30
|
|
|
20
31
|
/** A labelled textarea with its help line, built on `@foldkit/ui`'s `Textarea`: the label
|
|
21
|
-
* names the textarea, the help line describes it (`aria-describedby`)
|
|
22
|
-
* `aria-invalid
|
|
32
|
+
* names the textarea, the help line describes it (`aria-describedby`), an `error` sets
|
|
33
|
+
* `aria-invalid` and describes it before the help, and `isRequired` sets `aria-required`.
|
|
34
|
+
* Boxless like a Field, on a dotted baseline, with an ink corner grip. */
|
|
23
35
|
export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Message>): Html => {
|
|
24
|
-
const {
|
|
36
|
+
const {
|
|
37
|
+
id,
|
|
38
|
+
label,
|
|
39
|
+
value,
|
|
40
|
+
onInput,
|
|
41
|
+
help,
|
|
42
|
+
error,
|
|
43
|
+
isRequired = false,
|
|
44
|
+
isMono = false,
|
|
45
|
+
rows,
|
|
46
|
+
textareaAttributes = [],
|
|
47
|
+
} = config;
|
|
25
48
|
|
|
26
49
|
return UiTextarea.view(
|
|
27
50
|
{
|
|
28
51
|
id,
|
|
29
52
|
value,
|
|
30
53
|
onInput,
|
|
31
|
-
isInvalid,
|
|
54
|
+
isInvalid: error !== undefined,
|
|
32
55
|
hasDescription: help !== undefined,
|
|
33
56
|
...(rows !== undefined && { rows }),
|
|
34
57
|
toView: (groups) =>
|
|
35
58
|
h.div(
|
|
36
|
-
[h.Class(
|
|
59
|
+
[h.Class(`field${isMono ? " mono" : ""}`)],
|
|
37
60
|
[
|
|
38
61
|
h.label(groups.label, [label]),
|
|
39
|
-
|
|
62
|
+
// NOTE: @foldkit/ui gap: Textarea has no required option, so the wrapper says it.
|
|
63
|
+
h.textarea([
|
|
64
|
+
...textareaAttributes,
|
|
65
|
+
...groups.textarea,
|
|
66
|
+
...describedByError(id, error, help !== undefined, h),
|
|
67
|
+
...(isRequired ? [h.AriaRequired(true)] : []),
|
|
68
|
+
]),
|
|
69
|
+
...errorView(id, error, h),
|
|
40
70
|
...(help === undefined
|
|
41
71
|
? []
|
|
42
72
|
: [h.p([...groups.description, h.Class("field-help")], [help])]),
|