@expressive/ui 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +48 -2
- package/dist/button/base.d.ts +63 -0
- package/dist/button/base.js +90 -0
- package/dist/button/base.js.map +1 -0
- package/dist/button/button.d.ts +56 -0
- package/dist/button/button.js +138 -0
- package/dist/button/button.js.map +1 -0
- package/dist/calendar/base.d.ts +112 -0
- package/dist/calendar/base.js +235 -0
- package/dist/calendar/base.js.map +1 -0
- package/dist/calendar/calendar.d.ts +78 -0
- package/dist/calendar/calendar.js +145 -0
- package/dist/calendar/calendar.js.map +1 -0
- package/dist/calendar/date.js +56 -0
- package/dist/calendar/date.js.map +1 -0
- package/dist/dialog/base.d.ts +74 -0
- package/dist/dialog/base.js +143 -0
- package/dist/dialog/base.js.map +1 -0
- package/dist/dialog/dialog.d.ts +69 -0
- package/dist/dialog/dialog.js +108 -0
- package/dist/dialog/dialog.js.map +1 -0
- package/dist/dom.d.ts +16 -0
- package/dist/dom.js +17 -0
- package/dist/dropzone/base.d.ts +77 -0
- package/dist/dropzone/base.js +159 -0
- package/dist/dropzone/base.js.map +1 -0
- package/dist/dropzone/dropzone.d.ts +38 -0
- package/dist/dropzone/dropzone.js +92 -0
- package/dist/dropzone/dropzone.js.map +1 -0
- package/dist/form/base.d.ts +157 -0
- package/dist/form/base.js +243 -0
- package/dist/form/base.js.map +1 -0
- package/dist/form/form.d.ts +123 -0
- package/dist/form/form.js +179 -0
- package/dist/form/form.js.map +1 -0
- package/dist/icons/caret.d.ts +8 -0
- package/dist/icons/caret.js +16 -0
- package/dist/icons/caret.js.map +1 -0
- package/dist/icons/check.d.ts +8 -0
- package/dist/icons/check.js +16 -0
- package/dist/icons/check.js.map +1 -0
- package/dist/icons/cross.d.ts +8 -0
- package/dist/icons/cross.js +16 -0
- package/dist/icons/cross.js.map +1 -0
- package/dist/icons/icon.js +16 -0
- package/dist/icons/icon.js.map +1 -0
- package/dist/icons/spinner.d.ts +8 -0
- package/dist/icons/spinner.js +27 -0
- package/dist/icons/spinner.js.map +1 -0
- package/dist/icons/upload.d.ts +8 -0
- package/dist/icons/upload.js +16 -0
- package/dist/icons/upload.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/layout/base.d.ts +88 -0
- package/dist/layout/base.js +158 -0
- package/dist/layout/base.js.map +1 -0
- package/dist/layout/layout.d.ts +53 -0
- package/dist/layout/layout.js +80 -0
- package/dist/layout/layout.js.map +1 -0
- package/dist/piece.js +13 -0
- package/dist/piece.js.map +1 -0
- package/dist/popover/base.d.ts +94 -0
- package/dist/popover/base.js +176 -0
- package/dist/popover/base.js.map +1 -0
- package/dist/popover/place.d.ts +12 -0
- package/dist/popover/place.js +32 -0
- package/dist/popover/place.js.map +1 -0
- package/dist/popover/popover.d.ts +52 -0
- package/dist/popover/popover.js +104 -0
- package/dist/popover/popover.js.map +1 -0
- package/dist/select/base.d.ts +196 -0
- package/dist/select/base.js +409 -0
- package/dist/select/base.js.map +1 -0
- package/dist/select/select.d.ts +119 -0
- package/dist/select/select.js +219 -0
- package/dist/select/select.js.map +1 -0
- package/dist/table/base.d.ts +96 -0
- package/dist/table/base.js +154 -0
- package/dist/table/base.js.map +1 -0
- package/dist/table/column.d.ts +53 -0
- package/dist/table/column.js +29 -0
- package/dist/table/column.js.map +1 -0
- package/dist/table/table.d.ts +41 -0
- package/dist/table/table.js +101 -0
- package/dist/table/table.js.map +1 -0
- package/package.json +76 -4
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Dropzone as Dropzone$1 } from "./base.js";
|
|
2
|
+
import { Component } from "@expressive/mvc";
|
|
3
|
+
|
|
4
|
+
//#region src/dropzone/dropzone.d.ts
|
|
5
|
+
declare namespace Dropzone {
|
|
6
|
+
type Drag = Dropzone$1.Drag;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Drop target for files, with a click-to-browse fallback.
|
|
10
|
+
*
|
|
11
|
+
* Files not matching `accept` - extensions (`.pdf`), MIME types (`image/png`) or wildcards (`image/*`) - go to
|
|
12
|
+
* `onReject` and are counted in a message; browsers enforce `accept` only in the picker, not on drops. Without
|
|
13
|
+
* `multiple`, a drop of several files is rejected whole. While mounted, files dropped beside it do not navigate
|
|
14
|
+
* the page away. Pieces - `Root`, `Prompt`, `Message` - are function components reading `Dropzone.get()`, each
|
|
15
|
+
* styled on its own; pass one as a prop or assign it in a subclass to replace it. Stock pieces are statics.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* <Dropzone accept="image/*" hint="PNG or JPG, up to 5MB" onDrop={(files) => this.upload(files)} />
|
|
19
|
+
*/
|
|
20
|
+
declare class Dropzone extends Dropzone$1 {
|
|
21
|
+
static Root: ({
|
|
22
|
+
children
|
|
23
|
+
}: {
|
|
24
|
+
children: Component.Node;
|
|
25
|
+
}) => Component.Node;
|
|
26
|
+
static Prompt: () => Component.Node;
|
|
27
|
+
static Message: () => Component.Node;
|
|
28
|
+
Root: ({
|
|
29
|
+
children
|
|
30
|
+
}: {
|
|
31
|
+
children: Component.Node;
|
|
32
|
+
}) => Component.Node;
|
|
33
|
+
Prompt: () => Component.Node;
|
|
34
|
+
Message: () => Component.Node;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { Dropzone };
|
|
38
|
+
//# sourceMappingURL=dropzone.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Dropzone as Dropzone$1 } from "./base.js";
|
|
2
|
+
import { Upload } from "../icons/upload.js";
|
|
3
|
+
import { style } from "@expressive/dom";
|
|
4
|
+
import { jsx, jsxs } from "@expressive/dom/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/dropzone/dropzone.tsx
|
|
7
|
+
const Root = ({ children }) => /* @__PURE__ */ jsx("div", {
|
|
8
|
+
...Dropzone$1.get().rootProps,
|
|
9
|
+
children
|
|
10
|
+
});
|
|
11
|
+
style(Root, {
|
|
12
|
+
display: "flex",
|
|
13
|
+
flexDirection: "column",
|
|
14
|
+
gap: "8px"
|
|
15
|
+
});
|
|
16
|
+
const Prompt = () => {
|
|
17
|
+
const { label, hint, promptProps } = Dropzone$1.get();
|
|
18
|
+
const { active, props } = promptProps;
|
|
19
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
20
|
+
...props,
|
|
21
|
+
_active: active,
|
|
22
|
+
children: [
|
|
23
|
+
/* @__PURE__ */ jsx(Upload, {}),
|
|
24
|
+
/* @__PURE__ */ jsx("span", { children: label }),
|
|
25
|
+
hint && /* @__PURE__ */ jsx("small", { children: hint })
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
style(Prompt, {
|
|
30
|
+
display: "flex",
|
|
31
|
+
flexDirection: "column",
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
gap: "6px",
|
|
34
|
+
width: "100%",
|
|
35
|
+
padding: "28px 20px",
|
|
36
|
+
border: "1px dashed var(--ui-line, #ccc)",
|
|
37
|
+
borderRadius: "8px",
|
|
38
|
+
background: "var(--ui-surface, #fff)",
|
|
39
|
+
color: "var(--ui-muted, #999)",
|
|
40
|
+
font: "inherit",
|
|
41
|
+
cursor: "pointer",
|
|
42
|
+
transition: "border-color 100ms, background 100ms, color 100ms",
|
|
43
|
+
":hover": { borderColor: "var(--ui-accent, #2684ff)" },
|
|
44
|
+
":focus-visible": {
|
|
45
|
+
outline: "2px solid var(--ui-accent, #2684ff)",
|
|
46
|
+
outlineOffset: "2px"
|
|
47
|
+
},
|
|
48
|
+
":disabled": {
|
|
49
|
+
opacity: "0.5",
|
|
50
|
+
cursor: "not-allowed"
|
|
51
|
+
},
|
|
52
|
+
_span: { color: "var(--ui-text, inherit)" },
|
|
53
|
+
_small: { fontSize: "12px" },
|
|
54
|
+
_active: {
|
|
55
|
+
borderColor: "var(--ui-accent, #2684ff)",
|
|
56
|
+
background: "var(--ui-highlight, #deebff)",
|
|
57
|
+
color: "var(--ui-accent, #2684ff)"
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
const Message = () => /* @__PURE__ */ jsx("p", {
|
|
61
|
+
role: "alert",
|
|
62
|
+
children: Dropzone$1.get().message
|
|
63
|
+
});
|
|
64
|
+
style(Message, {
|
|
65
|
+
margin: "0",
|
|
66
|
+
color: "var(--ui-danger, #e5484d)",
|
|
67
|
+
fontSize: "13px"
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Drop target for files, with a click-to-browse fallback.
|
|
71
|
+
*
|
|
72
|
+
* Files not matching `accept` - extensions (`.pdf`), MIME types (`image/png`) or wildcards (`image/*`) - go to
|
|
73
|
+
* `onReject` and are counted in a message; browsers enforce `accept` only in the picker, not on drops. Without
|
|
74
|
+
* `multiple`, a drop of several files is rejected whole. While mounted, files dropped beside it do not navigate
|
|
75
|
+
* the page away. Pieces - `Root`, `Prompt`, `Message` - are function components reading `Dropzone.get()`, each
|
|
76
|
+
* styled on its own; pass one as a prop or assign it in a subclass to replace it. Stock pieces are statics.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* <Dropzone accept="image/*" hint="PNG or JPG, up to 5MB" onDrop={(files) => this.upload(files)} />
|
|
80
|
+
*/
|
|
81
|
+
var Dropzone = class extends Dropzone$1 {
|
|
82
|
+
static Root = Root;
|
|
83
|
+
static Prompt = Prompt;
|
|
84
|
+
static Message = Message;
|
|
85
|
+
Root = Root;
|
|
86
|
+
Prompt = Prompt;
|
|
87
|
+
Message = Message;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
export { Dropzone };
|
|
92
|
+
//# sourceMappingURL=dropzone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropzone.js","names":["Base.Dropzone"],"sources":["../../src/dropzone/dropzone.tsx"],"sourcesContent":["/** @jsxImportSource @expressive/dom */\nimport { Component } from '@expressive/mvc';\nimport { style } from '@expressive/dom';\n\nimport { Upload } from '../icons';\nimport * as Base from './base';\n\ndeclare namespace Dropzone {\n type Drag = Base.Dropzone.Drag;\n}\n\nconst Root = ({ children }: { children: Component.Node }): Component.Node => (\n <div {...Base.Dropzone.get().rootProps}>{children}</div>\n);\n\nstyle(Root, {\n display: 'flex',\n flexDirection: 'column',\n gap: '8px'\n});\n\nconst Prompt = (): Component.Node => {\n const { label, hint, promptProps } = Base.Dropzone.get();\n const { active, props } = promptProps;\n\n return (\n <button {...props} _active={active}>\n <Upload />\n <span>{label}</span>\n {hint && <small>{hint}</small>}\n </button>\n );\n};\n\nstyle(Prompt, {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n gap: '6px',\n width: '100%',\n padding: '28px 20px',\n border: '1px dashed var(--ui-line, #ccc)',\n borderRadius: '8px',\n background: 'var(--ui-surface, #fff)',\n color: 'var(--ui-muted, #999)',\n font: 'inherit',\n cursor: 'pointer',\n transition: 'border-color 100ms, background 100ms, color 100ms',\n ':hover': {\n borderColor: 'var(--ui-accent, #2684ff)'\n },\n ':focus-visible': {\n outline: '2px solid var(--ui-accent, #2684ff)',\n outlineOffset: '2px'\n },\n ':disabled': {\n opacity: '0.5',\n cursor: 'not-allowed'\n },\n _span: {\n color: 'var(--ui-text, inherit)'\n },\n _small: {\n fontSize: '12px'\n },\n _active: {\n borderColor: 'var(--ui-accent, #2684ff)',\n background: 'var(--ui-highlight, #deebff)',\n color: 'var(--ui-accent, #2684ff)'\n }\n});\n\nconst Message = (): Component.Node => <p role=\"alert\">{Base.Dropzone.get().message}</p>;\n\nstyle(Message, {\n margin: '0',\n color: 'var(--ui-danger, #e5484d)',\n fontSize: '13px'\n});\n\n/**\n * Drop target for files, with a click-to-browse fallback.\n *\n * Files not matching `accept` - extensions (`.pdf`), MIME types (`image/png`) or wildcards (`image/*`) - go to\n * `onReject` and are counted in a message; browsers enforce `accept` only in the picker, not on drops. Without\n * `multiple`, a drop of several files is rejected whole. While mounted, files dropped beside it do not navigate\n * the page away. Pieces - `Root`, `Prompt`, `Message` - are function components reading `Dropzone.get()`, each\n * styled on its own; pass one as a prop or assign it in a subclass to replace it. Stock pieces are statics.\n *\n * @example\n * <Dropzone accept=\"image/*\" hint=\"PNG or JPG, up to 5MB\" onDrop={(files) => this.upload(files)} />\n */\nclass Dropzone extends Base.Dropzone {\n static Root = Root;\n static Prompt = Prompt;\n static Message = Message;\n\n Root = Root;\n Prompt = Prompt;\n Message = Message;\n}\n\nexport { Dropzone, Message, Prompt, Root };\n"],"mappings":";;;;;;AAWA,MAAM,QAAQ,EAAE,eACd,oBAAC;CAAI,cAAkB,KAAK,CAAC;CAAY;EAAe;AAG1D,MAAM,MAAM;CACV,SAAS;CACT,eAAe;CACf,KAAK;CACN,CAAC;AAEF,MAAM,eAA+B;CACnC,MAAM,EAAE,OAAO,MAAM,2BAA8B,KAAK;CACxD,MAAM,EAAE,QAAQ,UAAU;AAE1B,QACE,qBAAC;EAAO,GAAI;EAAO,SAAS;;GAC1B,oBAAC,WAAS;GACV,oBAAC,oBAAM,QAAa;GACnB,QAAQ,oBAAC,qBAAO,OAAa;;GACvB;;AAIb,MAAM,QAAQ;CACZ,SAAS;CACT,eAAe;CACf,YAAY;CACZ,KAAK;CACL,OAAO;CACP,SAAS;CACT,QAAQ;CACR,cAAc;CACd,YAAY;CACZ,OAAO;CACP,MAAM;CACN,QAAQ;CACR,YAAY;CACZ,UAAU,EACR,aAAa,6BACd;CACD,kBAAkB;EAChB,SAAS;EACT,eAAe;EAChB;CACD,aAAa;EACX,SAAS;EACT,QAAQ;EACT;CACD,OAAO,EACL,OAAO,2BACR;CACD,QAAQ,EACN,UAAU,QACX;CACD,SAAS;EACP,aAAa;EACb,YAAY;EACZ,OAAO;EACR;CACF,CAAC;AAEF,MAAM,gBAAgC,oBAAC;CAAE,MAAK;sBAAuB,KAAK,CAAC;EAAY;AAEvF,MAAM,SAAS;CACb,QAAQ;CACR,OAAO;CACP,UAAU;CACX,CAAC;;;;;;;;;;;;;AAcF,IAAM,WAAN,cAAuBA,WAAc;CACnC,OAAO,OAAO;CACd,OAAO,SAAS;CAChB,OAAO,UAAU;CAEjB,OAAO;CACP,SAAS;CACT,UAAU"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Button } from "../button/base.js";
|
|
2
|
+
import { Component, Model } from "@expressive/mvc";
|
|
3
|
+
|
|
4
|
+
//#region src/form/base.d.ts
|
|
5
|
+
declare namespace Form {
|
|
6
|
+
/** The fields a subclass declares. */
|
|
7
|
+
type Values<T extends Form> = { [K in Exclude<keyof T, keyof Form>]: T[K] };
|
|
8
|
+
/** `false` or a message fails; anything else passes. */
|
|
9
|
+
type Check = boolean | string | void | undefined;
|
|
10
|
+
type Wrap = (props: {
|
|
11
|
+
children?: Component.Node;
|
|
12
|
+
}) => Component.Node;
|
|
13
|
+
type Fail = (props: {
|
|
14
|
+
error: Error;
|
|
15
|
+
}) => Component.Node;
|
|
16
|
+
}
|
|
17
|
+
declare namespace Field {
|
|
18
|
+
type Wrap = (props: {
|
|
19
|
+
children?: Component.Node;
|
|
20
|
+
}) => Component.Node;
|
|
21
|
+
type Part = () => Component.Node;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Form behavior without a look: values from declared fields, validation, submit and ARIA, assembled from pieces.
|
|
25
|
+
*
|
|
26
|
+
* `Root` and `Failure` are placeholders that throw when rendered - a skin assigns them. `Root` spreads `formProps`
|
|
27
|
+
* onto a `<form>`; `Failure` shows its `error` as an alert.
|
|
28
|
+
*/
|
|
29
|
+
declare class Form extends Component {
|
|
30
|
+
onSubmit?: (values: any) => unknown;
|
|
31
|
+
pending: boolean;
|
|
32
|
+
error?: Error;
|
|
33
|
+
Root: Form.Wrap;
|
|
34
|
+
Failure: Form.Fail;
|
|
35
|
+
constructor(...args: Model.Args);
|
|
36
|
+
get formProps(): {
|
|
37
|
+
readonly noValidate: true;
|
|
38
|
+
readonly 'aria-busy': boolean;
|
|
39
|
+
readonly onSubmit: (event?: {
|
|
40
|
+
preventDefault(): void;
|
|
41
|
+
}) => Promise<void>;
|
|
42
|
+
};
|
|
43
|
+
/** Current values, keyed by field. */
|
|
44
|
+
values(): Form.Values<this>;
|
|
45
|
+
/** Values that differ from the last successful submit, or from the start; `undefined` when none. */
|
|
46
|
+
changed(): Partial<Form.Values<this>> | undefined;
|
|
47
|
+
/** Restore values from the last successful submit and clear messages. */
|
|
48
|
+
reset(): void;
|
|
49
|
+
/** Check every field; resolves the ones that fail. */
|
|
50
|
+
invalid(): Promise<Field[]>;
|
|
51
|
+
/** Handle valid values; defaults to `onSubmit`. */
|
|
52
|
+
submit(values: Form.Values<this>): unknown;
|
|
53
|
+
/** Bring a field that failed into view on submit. Defaults to focusing the element with its `id`. */
|
|
54
|
+
protected reveal(field: Field): void;
|
|
55
|
+
send(event?: {
|
|
56
|
+
preventDefault(): void;
|
|
57
|
+
}): Promise<void>;
|
|
58
|
+
render(props: {
|
|
59
|
+
children?: Component.Node;
|
|
60
|
+
}): Component.Node;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Field behavior without a look: binding to the enclosing `Form`'s field `name`, checks and ARIA, assembled from
|
|
64
|
+
* pieces.
|
|
65
|
+
*
|
|
66
|
+
* Every piece is a placeholder that throws when rendered - a skin assigns each one. Pieces spread the matching
|
|
67
|
+
* `*Props` getter onto their element to stay accessible.
|
|
68
|
+
*/
|
|
69
|
+
declare class Field<T = any> extends Component {
|
|
70
|
+
name: string;
|
|
71
|
+
/** Defaults to `name`, split into words. */
|
|
72
|
+
label?: string;
|
|
73
|
+
type: string;
|
|
74
|
+
multiline: boolean;
|
|
75
|
+
placeholder?: string;
|
|
76
|
+
hint?: string;
|
|
77
|
+
optional: boolean;
|
|
78
|
+
disabled: boolean;
|
|
79
|
+
validate?: (value: T, form: Form) => Form.Check | Promise<Form.Check>;
|
|
80
|
+
/** Shown while the field fails a check. */
|
|
81
|
+
message?: string;
|
|
82
|
+
/** The input's id, for a `<label htmlFor>`. */
|
|
83
|
+
id: string;
|
|
84
|
+
form: Form;
|
|
85
|
+
Root: Field.Wrap;
|
|
86
|
+
Label: Field.Part;
|
|
87
|
+
Control: Field.Part;
|
|
88
|
+
Message: Field.Part;
|
|
89
|
+
constructor(...args: Model.Args);
|
|
90
|
+
get value(): T;
|
|
91
|
+
/** For the label element, with its `text`. */
|
|
92
|
+
get labelProps(): {
|
|
93
|
+
text: string;
|
|
94
|
+
props: {
|
|
95
|
+
htmlFor: string;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* For the control: a textarea when `multiline`, else an input of `type`. A number field writes numbers.
|
|
100
|
+
*
|
|
101
|
+
* `failed` is for styling.
|
|
102
|
+
*/
|
|
103
|
+
get controlProps(): {
|
|
104
|
+
failed: boolean;
|
|
105
|
+
props: {
|
|
106
|
+
type: string;
|
|
107
|
+
checked: boolean;
|
|
108
|
+
onChange: (event: Event) => void;
|
|
109
|
+
id: string;
|
|
110
|
+
name: string;
|
|
111
|
+
disabled: boolean;
|
|
112
|
+
'aria-required': boolean;
|
|
113
|
+
'aria-invalid': boolean;
|
|
114
|
+
'aria-describedby': string | undefined;
|
|
115
|
+
};
|
|
116
|
+
} | {
|
|
117
|
+
failed: boolean;
|
|
118
|
+
props: {
|
|
119
|
+
type: string | undefined;
|
|
120
|
+
placeholder: string | undefined;
|
|
121
|
+
value: string;
|
|
122
|
+
onInput: (event: Event) => void;
|
|
123
|
+
id: string;
|
|
124
|
+
name: string;
|
|
125
|
+
disabled: boolean;
|
|
126
|
+
'aria-required': boolean;
|
|
127
|
+
'aria-invalid': boolean;
|
|
128
|
+
'aria-describedby': string | undefined;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
/** For the message element, with its `text` and `failed` for styling. */
|
|
132
|
+
get messageProps(): {
|
|
133
|
+
text: string | undefined;
|
|
134
|
+
failed: boolean;
|
|
135
|
+
props: {
|
|
136
|
+
id: string;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
/** Write a value to the form, rechecking if this field has failed. */
|
|
140
|
+
update(value: T): void;
|
|
141
|
+
/** Run the required check and `validate`; resolves whether the field passes. */
|
|
142
|
+
check(): Promise<boolean>;
|
|
143
|
+
render(): Component.Node;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Submit button for the enclosing `Form`; shows pending while it submits.
|
|
147
|
+
*
|
|
148
|
+
* Its pieces are `Button`'s.
|
|
149
|
+
*/
|
|
150
|
+
declare class Submit extends Button {
|
|
151
|
+
type: "submit";
|
|
152
|
+
form: Form;
|
|
153
|
+
constructor(...args: Model.Args);
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
export { Field, Form, Submit };
|
|
157
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { missing } from "../piece.js";
|
|
2
|
+
import { Button } from "../button/base.js";
|
|
3
|
+
import { Component, get } from "@expressive/mvc";
|
|
4
|
+
import { jsx, jsxs } from "@expressive/mvc/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/form/base.tsx
|
|
7
|
+
const BASE = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
const FIELDS = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
const SAVED = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
let ID = 0;
|
|
11
|
+
/**
|
|
12
|
+
* Form behavior without a look: values from declared fields, validation, submit and ARIA, assembled from pieces.
|
|
13
|
+
*
|
|
14
|
+
* `Root` and `Failure` are placeholders that throw when rendered - a skin assigns them. `Root` spreads `formProps`
|
|
15
|
+
* onto a `<form>`; `Failure` shows its `error` as an alert.
|
|
16
|
+
*/
|
|
17
|
+
var Form = class extends Component {
|
|
18
|
+
onSubmit = void 0;
|
|
19
|
+
pending = false;
|
|
20
|
+
error = void 0;
|
|
21
|
+
Root = missing("Form.Root");
|
|
22
|
+
Failure = missing("Form.Failure");
|
|
23
|
+
constructor(...args) {
|
|
24
|
+
super(...args, () => {
|
|
25
|
+
SAVED.set(this, this.values());
|
|
26
|
+
});
|
|
27
|
+
BASE.set(this, new Set(Object.keys(this)));
|
|
28
|
+
}
|
|
29
|
+
get formProps() {
|
|
30
|
+
return {
|
|
31
|
+
noValidate: true,
|
|
32
|
+
"aria-busy": this.pending,
|
|
33
|
+
onSubmit: this.send
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Current values, keyed by field. */
|
|
37
|
+
values() {
|
|
38
|
+
const base = BASE.get(this);
|
|
39
|
+
const values = {};
|
|
40
|
+
for (const key of Object.keys(this)) if (!base.has(key) && !computed(this, key)) values[key] = this[key];
|
|
41
|
+
return values;
|
|
42
|
+
}
|
|
43
|
+
/** Values that differ from the last successful submit, or from the start; `undefined` when none. */
|
|
44
|
+
changed() {
|
|
45
|
+
const saved = SAVED.get(this);
|
|
46
|
+
const values = this.values();
|
|
47
|
+
const changed = {};
|
|
48
|
+
for (const key in values) if (values[key] !== saved[key]) changed[key] = values[key];
|
|
49
|
+
return Object.keys(changed).length ? changed : void 0;
|
|
50
|
+
}
|
|
51
|
+
/** Restore values from the last successful submit and clear messages. */
|
|
52
|
+
reset() {
|
|
53
|
+
Object.assign(this, SAVED.get(this));
|
|
54
|
+
this.error = void 0;
|
|
55
|
+
for (const field of FIELDS.get(this) || []) field.message = void 0;
|
|
56
|
+
}
|
|
57
|
+
/** Check every field; resolves the ones that fail. */
|
|
58
|
+
async invalid() {
|
|
59
|
+
const fields = [...FIELDS.get(this) || []];
|
|
60
|
+
const passed = await Promise.all(fields.map((field) => field.check()));
|
|
61
|
+
return fields.filter((_, index) => !passed[index]);
|
|
62
|
+
}
|
|
63
|
+
/** Handle valid values; defaults to `onSubmit`. */
|
|
64
|
+
submit(values) {
|
|
65
|
+
return this.onSubmit && this.onSubmit(values);
|
|
66
|
+
}
|
|
67
|
+
/** Bring a field that failed into view on submit. Defaults to focusing the element with its `id`. */
|
|
68
|
+
reveal(field) {
|
|
69
|
+
document.getElementById(field.id)?.focus();
|
|
70
|
+
}
|
|
71
|
+
async send(event) {
|
|
72
|
+
if (event) event.preventDefault();
|
|
73
|
+
if (this.pending) return;
|
|
74
|
+
this.error = void 0;
|
|
75
|
+
const [first] = await this.invalid();
|
|
76
|
+
if (first) {
|
|
77
|
+
this.reveal(first);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const values = this.values();
|
|
81
|
+
this.pending = true;
|
|
82
|
+
try {
|
|
83
|
+
await this.submit(values);
|
|
84
|
+
SAVED.set(this, values);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
this.error = error instanceof Error ? error : new Error(String(error));
|
|
87
|
+
}
|
|
88
|
+
this.pending = false;
|
|
89
|
+
}
|
|
90
|
+
render(props) {
|
|
91
|
+
const { error } = this;
|
|
92
|
+
return /* @__PURE__ */ jsxs(this.Root, { children: [props.children, error && /* @__PURE__ */ jsx(this.Failure, { error })] });
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Field behavior without a look: binding to the enclosing `Form`'s field `name`, checks and ARIA, assembled from
|
|
97
|
+
* pieces.
|
|
98
|
+
*
|
|
99
|
+
* Every piece is a placeholder that throws when rendered - a skin assigns each one. Pieces spread the matching
|
|
100
|
+
* `*Props` getter onto their element to stay accessible.
|
|
101
|
+
*/
|
|
102
|
+
var Field = class extends Component {
|
|
103
|
+
name = "";
|
|
104
|
+
/** Defaults to `name`, split into words. */
|
|
105
|
+
label = void 0;
|
|
106
|
+
type = "text";
|
|
107
|
+
multiline = false;
|
|
108
|
+
placeholder = void 0;
|
|
109
|
+
hint = void 0;
|
|
110
|
+
optional = false;
|
|
111
|
+
disabled = false;
|
|
112
|
+
validate = void 0;
|
|
113
|
+
/** Shown while the field fails a check. */
|
|
114
|
+
message = void 0;
|
|
115
|
+
/** The input's id, for a `<label htmlFor>`. */
|
|
116
|
+
id = `field-${++ID}`;
|
|
117
|
+
form = get(Form);
|
|
118
|
+
Root = missing("Field.Root");
|
|
119
|
+
Label = missing("Field.Label");
|
|
120
|
+
Control = missing("Field.Control");
|
|
121
|
+
Message = missing("Field.Message");
|
|
122
|
+
constructor(...args) {
|
|
123
|
+
super(...args, () => {
|
|
124
|
+
this.get(({ form }) => {
|
|
125
|
+
const fields = FIELDS.get(form.is) || /* @__PURE__ */ new Set();
|
|
126
|
+
FIELDS.set(form.is, fields.add(this));
|
|
127
|
+
return () => fields.delete(this);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
get value() {
|
|
132
|
+
return this.form[this.name];
|
|
133
|
+
}
|
|
134
|
+
/** For the label element, with its `text`. */
|
|
135
|
+
get labelProps() {
|
|
136
|
+
const { id, label, name } = this;
|
|
137
|
+
return {
|
|
138
|
+
text: label ?? words(name),
|
|
139
|
+
props: { htmlFor: id }
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* For the control: a textarea when `multiline`, else an input of `type`. A number field writes numbers.
|
|
144
|
+
*
|
|
145
|
+
* `failed` is for styling.
|
|
146
|
+
*/
|
|
147
|
+
get controlProps() {
|
|
148
|
+
const { id, name, type, multiline, placeholder, disabled, optional, message, hint, value } = this;
|
|
149
|
+
const common = {
|
|
150
|
+
id,
|
|
151
|
+
name,
|
|
152
|
+
disabled,
|
|
153
|
+
"aria-required": !optional,
|
|
154
|
+
"aria-invalid": !!message,
|
|
155
|
+
"aria-describedby": message || hint ? `${id}-message` : void 0
|
|
156
|
+
};
|
|
157
|
+
if (type == "checkbox") return {
|
|
158
|
+
failed: !!message,
|
|
159
|
+
props: {
|
|
160
|
+
...common,
|
|
161
|
+
type,
|
|
162
|
+
checked: !!value,
|
|
163
|
+
onChange: (event) => this.update(event.currentTarget.checked)
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
failed: !!message,
|
|
168
|
+
props: {
|
|
169
|
+
...common,
|
|
170
|
+
type: multiline ? void 0 : type,
|
|
171
|
+
placeholder,
|
|
172
|
+
value: String(value ?? ""),
|
|
173
|
+
onInput: (event) => {
|
|
174
|
+
const input = event.currentTarget;
|
|
175
|
+
this.update(typeof value == "number" ? input.valueAsNumber : input.value);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** For the message element, with its `text` and `failed` for styling. */
|
|
181
|
+
get messageProps() {
|
|
182
|
+
const { id, message, hint } = this;
|
|
183
|
+
return {
|
|
184
|
+
text: message || hint,
|
|
185
|
+
failed: !!message,
|
|
186
|
+
props: { id: `${id}-message` }
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** Write a value to the form, rechecking if this field has failed. */
|
|
190
|
+
update(value) {
|
|
191
|
+
this.form[this.name] = value;
|
|
192
|
+
if (this.message) this.check();
|
|
193
|
+
}
|
|
194
|
+
/** Run the required check and `validate`; resolves whether the field passes. */
|
|
195
|
+
async check() {
|
|
196
|
+
const { value, optional, validate, form } = this;
|
|
197
|
+
const empty = value === void 0 || value === null || value === "" || value === false;
|
|
198
|
+
let result = optional || !empty || "Required";
|
|
199
|
+
if (result === true && validate && !(optional && empty)) result = await validate(value, form);
|
|
200
|
+
this.message = result === false ? "Invalid" : typeof result == "string" ? result : void 0;
|
|
201
|
+
return !this.message;
|
|
202
|
+
}
|
|
203
|
+
render() {
|
|
204
|
+
const { message, hint, type } = this;
|
|
205
|
+
const check = type == "checkbox";
|
|
206
|
+
return /* @__PURE__ */ jsxs(this.Root, { children: [
|
|
207
|
+
!check && /* @__PURE__ */ jsx(this.Label, {}),
|
|
208
|
+
/* @__PURE__ */ jsx(this.Control, {}),
|
|
209
|
+
check && /* @__PURE__ */ jsx(this.Label, {}),
|
|
210
|
+
(message || hint) && /* @__PURE__ */ jsx(this.Message, {})
|
|
211
|
+
] });
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Submit button for the enclosing `Form`; shows pending while it submits.
|
|
216
|
+
*
|
|
217
|
+
* Its pieces are `Button`'s.
|
|
218
|
+
*/
|
|
219
|
+
var Submit = class extends Button {
|
|
220
|
+
type = "submit";
|
|
221
|
+
form = get(Form);
|
|
222
|
+
constructor(...args) {
|
|
223
|
+
super(...args, () => {
|
|
224
|
+
this.get(({ form }) => {
|
|
225
|
+
this.pending = form.pending;
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
function words(name) {
|
|
231
|
+
return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (first) => first.toUpperCase());
|
|
232
|
+
}
|
|
233
|
+
function computed(target, key) {
|
|
234
|
+
for (let proto = Object.getPrototypeOf(target); proto; proto = Object.getPrototypeOf(proto)) {
|
|
235
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
|
|
236
|
+
if (descriptor) return !!descriptor.get;
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
//#endregion
|
|
242
|
+
export { Field, Form, Submit };
|
|
243
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","names":[],"sources":["../../src/form/base.tsx"],"sourcesContent":["import { Component, get, type Model } from '@expressive/mvc';\n\nimport { Button } from '../button/base';\nimport { missing } from '../piece';\n\ndeclare namespace Form {\n /** The fields a subclass declares. */\n type Values<T extends Form> = { [K in Exclude<keyof T, keyof Form>]: T[K] };\n\n /** `false` or a message fails; anything else passes. */\n type Check = boolean | string | void | undefined;\n\n type Wrap = (props: { children?: Component.Node }) => Component.Node;\n type Fail = (props: { error: Error }) => Component.Node;\n}\n\ndeclare namespace Field {\n type Wrap = (props: { children?: Component.Node }) => Component.Node;\n type Part = () => Component.Node;\n}\n\nconst BASE = new WeakMap<Form, Set<string>>();\n\nconst FIELDS = new WeakMap<Form, Set<Field>>();\n\nconst SAVED = new WeakMap<Form, Record<string, unknown>>();\n\nlet ID = 0;\n\n/**\n * Form behavior without a look: values from declared fields, validation, submit and ARIA, assembled from pieces.\n *\n * `Root` and `Failure` are placeholders that throw when rendered - a skin assigns them. `Root` spreads `formProps`\n * onto a `<form>`; `Failure` shows its `error` as an alert.\n */\nclass Form extends Component {\n onSubmit?: (values: any) => unknown = undefined;\n\n pending = false;\n error?: Error = undefined;\n\n Root: Form.Wrap = missing('Form.Root');\n Failure: Form.Fail = missing('Form.Failure');\n\n constructor(...args: Model.Args) {\n super(...args, () => {\n SAVED.set(this, this.values());\n });\n\n BASE.set(this, new Set(Object.keys(this)));\n }\n\n get formProps() {\n return { noValidate: true, 'aria-busy': this.pending, onSubmit: this.send } as const;\n }\n\n /** Current values, keyed by field. */\n values(): Form.Values<this> {\n const base = BASE.get(this)!;\n const values: Record<string, unknown> = {};\n\n for (const key of Object.keys(this)) if (!base.has(key) && !computed(this, key)) values[key] = (this as any)[key];\n\n return values as Form.Values<this>;\n }\n\n /** Values that differ from the last successful submit, or from the start; `undefined` when none. */\n changed(): Partial<Form.Values<this>> | undefined {\n const saved = SAVED.get(this)!;\n const values = this.values() as Record<string, unknown>;\n const changed: Record<string, unknown> = {};\n\n for (const key in values) if (values[key] !== saved[key]) changed[key] = values[key];\n\n return Object.keys(changed).length ? (changed as Partial<Form.Values<this>>) : undefined;\n }\n\n /** Restore values from the last successful submit and clear messages. */\n reset() {\n Object.assign(this, SAVED.get(this));\n this.error = undefined;\n\n for (const field of FIELDS.get(this) || []) field.message = undefined;\n }\n\n /** Check every field; resolves the ones that fail. */\n async invalid(): Promise<Field[]> {\n const fields = [...(FIELDS.get(this) || [])];\n const passed = await Promise.all(fields.map((field) => field.check()));\n\n return fields.filter((_, index) => !passed[index]);\n }\n\n /** Handle valid values; defaults to `onSubmit`. */\n submit(values: Form.Values<this>): unknown {\n return this.onSubmit && this.onSubmit(values);\n }\n\n /** Bring a field that failed into view on submit. Defaults to focusing the element with its `id`. */\n protected reveal(field: Field) {\n document.getElementById(field.id)?.focus();\n }\n\n async send(event?: { preventDefault(): void }) {\n if (event) event.preventDefault();\n if (this.pending) return;\n\n this.error = undefined;\n\n const [first] = await this.invalid();\n\n if (first) {\n this.reveal(first);\n return;\n }\n\n const values = this.values();\n\n this.pending = true;\n\n try {\n await this.submit(values);\n SAVED.set(this, values);\n } catch (error) {\n this.error = error instanceof Error ? error : new Error(String(error));\n }\n\n this.pending = false;\n }\n\n render(props: { children?: Component.Node }): Component.Node {\n const { error } = this;\n\n return (\n <this.Root>\n {props.children}\n {error && <this.Failure error={error} />}\n </this.Root>\n );\n }\n}\n\n/**\n * Field behavior without a look: binding to the enclosing `Form`'s field `name`, checks and ARIA, assembled from\n * pieces.\n *\n * Every piece is a placeholder that throws when rendered - a skin assigns each one. Pieces spread the matching\n * `*Props` getter onto their element to stay accessible.\n */\nclass Field<T = any> extends Component {\n name = '';\n\n /** Defaults to `name`, split into words. */\n label?: string = undefined;\n\n type = 'text';\n multiline = false;\n placeholder?: string = undefined;\n hint?: string = undefined;\n optional = false;\n disabled = false;\n\n validate?: (value: T, form: Form) => Form.Check | Promise<Form.Check> = undefined;\n\n /** Shown while the field fails a check. */\n message?: string = undefined;\n\n /** The input's id, for a `<label htmlFor>`. */\n id = `field-${++ID}`;\n\n form = get(Form);\n\n Root: Field.Wrap = missing('Field.Root');\n Label: Field.Part = missing('Field.Label');\n Control: Field.Part = missing('Field.Control');\n Message: Field.Part = missing('Field.Message');\n\n constructor(...args: Model.Args) {\n super(...args, () => {\n this.get(({ form }) => {\n const fields = FIELDS.get(form.is) || new Set();\n\n FIELDS.set(form.is, fields.add(this));\n\n return () => fields.delete(this);\n });\n });\n }\n\n get value(): T {\n return (this.form as any)[this.name];\n }\n\n /** For the label element, with its `text`. */\n get labelProps() {\n const { id, label, name } = this;\n\n return { text: label ?? words(name), props: { htmlFor: id } };\n }\n\n /**\n * For the control: a textarea when `multiline`, else an input of `type`. A number field writes numbers.\n *\n * `failed` is for styling.\n */\n get controlProps() {\n const { id, name, type, multiline, placeholder, disabled, optional, message, hint, value } = this;\n const common = {\n id,\n name,\n disabled,\n 'aria-required': !optional,\n 'aria-invalid': !!message,\n 'aria-describedby': message || hint ? `${id}-message` : undefined\n };\n\n if (type == 'checkbox')\n return {\n failed: !!message,\n props: {\n ...common,\n type,\n checked: !!value,\n onChange: (event: Event) => this.update((event.currentTarget as HTMLInputElement).checked as T)\n }\n };\n\n return {\n failed: !!message,\n props: {\n ...common,\n type: multiline ? undefined : type,\n placeholder,\n value: String(value ?? ''),\n onInput: (event: Event) => {\n const input = event.currentTarget as HTMLInputElement;\n\n this.update((typeof value == 'number' ? input.valueAsNumber : input.value) as T);\n }\n }\n };\n }\n\n /** For the message element, with its `text` and `failed` for styling. */\n get messageProps() {\n const { id, message, hint } = this;\n\n return { text: message || hint, failed: !!message, props: { id: `${id}-message` } };\n }\n\n /** Write a value to the form, rechecking if this field has failed. */\n update(value: T) {\n (this.form as any)[this.name] = value;\n\n if (this.message) this.check();\n }\n\n /** Run the required check and `validate`; resolves whether the field passes. */\n async check(): Promise<boolean> {\n const { value, optional, validate, form } = this;\n const empty = value === undefined || value === null || value === '' || value === (false as T);\n let result: Form.Check = optional || !empty || 'Required';\n\n if (result === true && validate && !(optional && empty)) result = await validate(value, form);\n\n this.message = result === false ? 'Invalid' : typeof result == 'string' ? result : undefined;\n\n return !this.message;\n }\n\n render(): Component.Node {\n const { message, hint, type } = this;\n const check = type == 'checkbox';\n\n return (\n <this.Root>\n {!check && <this.Label />}\n <this.Control />\n {check && <this.Label />}\n {(message || hint) && <this.Message />}\n </this.Root>\n );\n }\n}\n\n/**\n * Submit button for the enclosing `Form`; shows pending while it submits.\n *\n * Its pieces are `Button`'s.\n */\nclass Submit extends Button {\n type = 'submit' as const;\n\n form = get(Form);\n\n constructor(...args: Model.Args) {\n super(...args, () => {\n this.get(({ form }) => {\n this.pending = form.pending;\n });\n });\n }\n}\n\nfunction words(name: string) {\n return name.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^./, (first) => first.toUpperCase());\n}\n\nfunction computed(target: object, key: string) {\n for (let proto = Object.getPrototypeOf(target); proto; proto = Object.getPrototypeOf(proto)) {\n const descriptor = Object.getOwnPropertyDescriptor(proto, key);\n\n if (descriptor) return !!descriptor.get;\n }\n\n return false;\n}\n\nexport { Field, Form, Submit };\n"],"mappings":"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"}
|