@excom/super-form 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/.rush/temp/chunked-rush-logs/super-form.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/super-form.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/super-form.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_docs/all.log +1 -0
- package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_docs/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +3 -0
- package/config/rig.json +5 -0
- package/index.ts +17 -0
- package/package.json +46 -0
- package/rush-logs/super-form.apply-exports.cache.log +1 -0
- package/rush-logs/super-form.apply-exports.log +1 -0
- package/rush-logs/super-form.build_docs.cache.log +1 -0
- package/rush-logs/super-form.build_docs.log +1 -0
- package/rush-logs/super-form.build_package-metas.cache.log +1 -0
- package/rush-logs/super-form.build_package-metas.log +1 -0
- package/super-form.ts +100 -0
- package/support/custom-elements.json +107 -0
- package/support/demos/external-trigger.html +43 -0
- package/support/demos/simple.html +31 -0
- package/support/dist-docs/super-form.md +212 -0
- package/support/docs/README.md +61 -0
- package/support/package-meta.json +224 -0
- package/support/tests/__snapshots__/external-trigger.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/simple.view.test.ts.snap +23 -0
- package/support/tests/external-trigger.view.test.ts +57 -0
- package/support/tests/simple.view.test.ts +63 -0
- package/support/tests/super-form.test.ts +319 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
|
package/config/rig.json
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SuperForm } from "./super-form";
|
|
2
|
+
|
|
3
|
+
SuperForm.define();
|
|
4
|
+
|
|
5
|
+
export { SuperForm };
|
|
6
|
+
|
|
7
|
+
type T_HTMLSuperFormElement = typeof SuperForm.CustomElement;
|
|
8
|
+
declare global {
|
|
9
|
+
interface HTMLSuperFormElement extends T_HTMLSuperFormElement {}
|
|
10
|
+
interface Window {
|
|
11
|
+
HTMLSuperFormElement: HTMLSuperFormElement;
|
|
12
|
+
}
|
|
13
|
+
interface HTMLElementTagNameMap {
|
|
14
|
+
"super-form": HTMLSuperFormElement;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export type { HTMLSuperFormElement };
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@excom/super-form",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "<super-form> custom element",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.13.0"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@excom/fetchable-element": "^0.1.0",
|
|
12
|
+
"@excom/neutron": "^0.1.0"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@excom/heft-rig": "^0.1.0",
|
|
17
|
+
"@excom/kit-logger": "^0.1.0",
|
|
18
|
+
"@excom/quark": "^0.1.0",
|
|
19
|
+
"@excom/quark-sheet": "^0.1.0"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"url": "excom-dev/nucleus",
|
|
23
|
+
"directory": "packages/super-form"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/super-form/support/docs/README.md",
|
|
26
|
+
"bugs": "https://github.com/excom-dev/nucleus/issues",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"super-form",
|
|
29
|
+
"neutron",
|
|
30
|
+
"custom-elements"
|
|
31
|
+
],
|
|
32
|
+
"excom": {
|
|
33
|
+
"packageType": "kit-element"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
|
|
37
|
+
"build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
|
|
38
|
+
"format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
|
|
39
|
+
"test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
|
|
40
|
+
"coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
|
|
41
|
+
"dev": "node node_modules/@excom/heft-rig/scripts/vite-dev.mjs",
|
|
42
|
+
"preview": "node node_modules/@excom/heft-rig/scripts/vite-preview.mjs",
|
|
43
|
+
"build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs",
|
|
44
|
+
"build:docs": "node node_modules/@excom/heft-rig/scripts/build-docs.mjs"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Caching has been disabled for this project's "apply-exports" command.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This project does not define the caching behavior of the "build:docs" command, so caching has been disabled.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
|
package/super-form.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { FetchableElement } from "@excom/fetchable-element";
|
|
2
|
+
import { Neutron, TEvent } from "@excom/neutron";
|
|
3
|
+
|
|
4
|
+
export type FetchArgs = [url: string, requestInit: RequestInit];
|
|
5
|
+
|
|
6
|
+
export type SuperFormSubmitEvent = TEvent & {
|
|
7
|
+
type: "super-form-submit";
|
|
8
|
+
detail: FetchArgs;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Native form `submit` this element intercepts, not a Neutron emit. */
|
|
12
|
+
export type SuperFormNativeSubmitEvent = SubmitEvent & {
|
|
13
|
+
type: "submit";
|
|
14
|
+
bubbles: true;
|
|
15
|
+
cancelable: true;
|
|
16
|
+
composed: false;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Turns a plain `<form>` into a zero-JS `fetch()` submit — intercepts
|
|
21
|
+
* `submit`, converts the form's fields into a JSON payload (see
|
|
22
|
+
* `formToJson()` below), and sends it to the form's `action` (or
|
|
23
|
+
* `api-url` / `api-method`, if no `form-ref` form is found). Composes
|
|
24
|
+
* `FetchableElement`, so `is-loading` / `is-success` / `is-error` / `provision`
|
|
25
|
+
* and every request-building attribute (headers, credentials, …) are
|
|
26
|
+
* inherited — see that package's docs for the full list.
|
|
27
|
+
*
|
|
28
|
+
* Form field `name`s become JSON keys: dot-separated names nest
|
|
29
|
+
* (`address.city` → `{ address: { city } }`) and a trailing `[]` collects
|
|
30
|
+
* same-named fields into an array (`tags[]` → `{ tags: [...] }`).
|
|
31
|
+
*
|
|
32
|
+
* @summary Zero-JS `fetch()` submit for a plain `<form>`.
|
|
33
|
+
*
|
|
34
|
+
* @fires super-form-submit - Internal — dispatched whenever a submit is
|
|
35
|
+
* about to run (real `submit` or the `--submit` command). Built by
|
|
36
|
+
* `getFetchArgs()`. Cancelable; default action calls `doFetch()`.
|
|
37
|
+
* @type SuperFormSubmitEvent
|
|
38
|
+
* @listens submit - The default action of the `<form>` matched by
|
|
39
|
+
* `form-ref` (or any descendant `<form>`); prevented, then converted
|
|
40
|
+
* into `super-form-submit`.
|
|
41
|
+
* @type SuperFormNativeSubmitEvent
|
|
42
|
+
* @command --submit - Submits programmatically (`<button command="--submit"
|
|
43
|
+
* commandfor="…">`) — the only option when `form-ref` points to a form
|
|
44
|
+
* that isn't a descendant, since this element can't hear its `submit`
|
|
45
|
+
* event directly.
|
|
46
|
+
* @default-action super-form-submit - Calls `doFetch(url, requestInit)`
|
|
47
|
+
* with the event's detail.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* <super-form>
|
|
51
|
+
* <form action="/api/signup" method="post">
|
|
52
|
+
* <input name="email" type="email" required>
|
|
53
|
+
* <button type="submit">Sign up</button>
|
|
54
|
+
* </form>
|
|
55
|
+
* </super-form>
|
|
56
|
+
*/
|
|
57
|
+
export const SuperForm = Neutron.compose([
|
|
58
|
+
FetchableElement,
|
|
59
|
+
Neutron({
|
|
60
|
+
tag: "super-form",
|
|
61
|
+
props: {
|
|
62
|
+
/**
|
|
63
|
+
* @option
|
|
64
|
+
* CSS selector for the `<form>` to intercept. The form's
|
|
65
|
+
* `action` / `method` / `enctype` take priority over `api-url` /
|
|
66
|
+
* `api-method` below. Must be a descendant to be heard directly —
|
|
67
|
+
* point elsewhere and invoke the `--submit` command instead.
|
|
68
|
+
* @default :scope form
|
|
69
|
+
* @values <CSS Selector>
|
|
70
|
+
*/
|
|
71
|
+
formRef: {
|
|
72
|
+
type: String,
|
|
73
|
+
// Non-descendant form: this element won't hear `submit`; invoke `--submit`
|
|
74
|
+
defaultValue: () => ":scope form",
|
|
75
|
+
},
|
|
76
|
+
/**
|
|
77
|
+
* @option
|
|
78
|
+
* HTTP method used when the form itself has no `method`.
|
|
79
|
+
* @default POST
|
|
80
|
+
*/
|
|
81
|
+
apiMethod: {
|
|
82
|
+
type: String,
|
|
83
|
+
defaultValue: () => "POST",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
])
|
|
88
|
+
.onEvent("submit", ({ getFetchArgs }, e) => {
|
|
89
|
+
// Block the native submit
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
return {
|
|
92
|
+
emit: ["super-form-submit", { detail: [getFetchArgs()] }],
|
|
93
|
+
};
|
|
94
|
+
})
|
|
95
|
+
.onCommand("--submit", ({ getFetchArgs }) => ({
|
|
96
|
+
emit: ["super-form-submit", { detail: [getFetchArgs()] }],
|
|
97
|
+
}))
|
|
98
|
+
.onEventDefault("super-form-submit", (_, { detail }) => ({
|
|
99
|
+
doFetch: [detail[0], detail[1]],
|
|
100
|
+
}));
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0.0",
|
|
3
|
+
"modules": [
|
|
4
|
+
{
|
|
5
|
+
"kind": "javascript-module",
|
|
6
|
+
"path": "super-form.ts",
|
|
7
|
+
"declarations": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "class",
|
|
10
|
+
"name": "SuperForm",
|
|
11
|
+
"customElement": true,
|
|
12
|
+
"tagName": "super-form",
|
|
13
|
+
"mixins": [
|
|
14
|
+
{
|
|
15
|
+
"name": "FetchableElement",
|
|
16
|
+
"package": "@excom/fetchable-element"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"summary": "Zero-JS `fetch()` submit for a plain `<form>`.",
|
|
20
|
+
"description": "Native form `submit` this element intercepts, not a Neutron emit.",
|
|
21
|
+
"attributes": [
|
|
22
|
+
{
|
|
23
|
+
"name": "form-ref",
|
|
24
|
+
"type": {
|
|
25
|
+
"text": "string"
|
|
26
|
+
},
|
|
27
|
+
"description": "CSS selector for the `<form>` to intercept. The form's `action` / `method` / `enctype` take priority over `api-url` / `api-method` below. Must be a descendant to be heard directly — point elsewhere and invoke the `--submit` command instead.",
|
|
28
|
+
"fieldName": "formRef",
|
|
29
|
+
"default": ":scope form",
|
|
30
|
+
"values": [
|
|
31
|
+
"<CSS Selector>"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"members": [
|
|
36
|
+
{
|
|
37
|
+
"kind": "field",
|
|
38
|
+
"name": "formRef",
|
|
39
|
+
"type": {
|
|
40
|
+
"text": "string"
|
|
41
|
+
},
|
|
42
|
+
"privacy": "public",
|
|
43
|
+
"readonly": false,
|
|
44
|
+
"description": "CSS selector for the `<form>` to intercept. The form's `action` / `method` / `enctype` take priority over `api-url` / `api-method` below. Must be a descendant to be heard directly — point elsewhere and invoke the `--submit` command instead.",
|
|
45
|
+
"default": ":scope form",
|
|
46
|
+
"_neutron": {
|
|
47
|
+
"surface": "option"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"events": [
|
|
52
|
+
{
|
|
53
|
+
"name": "super-form-submit",
|
|
54
|
+
"description": "Internal — dispatched whenever a submit is about to run (real `submit` or the `--submit` command). Built by `getFetchArgs()`. Cancelable; default action calls `doFetch()`.",
|
|
55
|
+
"type": {
|
|
56
|
+
"text": "SuperFormSubmitEvent",
|
|
57
|
+
"expanded": "CustomEvent & { type: \"super-form-submit\"; detail: [url: string, requestInit: RequestInit]; bubbles: true; cancelable: true; composed: true }"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"_neutron": {
|
|
62
|
+
"listens": [
|
|
63
|
+
{
|
|
64
|
+
"name": "submit",
|
|
65
|
+
"description": "The default action of the `<form>` matched by `form-ref` (or any descendant `<form>`); prevented, then converted into `super-form-submit`.",
|
|
66
|
+
"type": {
|
|
67
|
+
"text": "SuperFormNativeSubmitEvent",
|
|
68
|
+
"expanded": "SubmitEvent & { type: \"submit\"; bubbles: true; cancelable: true; composed: false; }"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"commands": [
|
|
73
|
+
{
|
|
74
|
+
"name": "--submit",
|
|
75
|
+
"description": "Submits programmatically (`<button command=\"--submit\" commandfor=\"…\">`) — the only option when `form-ref` points to a form that isn't a descendant, since this element can't hear its `submit` event directly."
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"defaultActions": [
|
|
79
|
+
{
|
|
80
|
+
"name": "super-form-submit",
|
|
81
|
+
"description": "Calls `doFetch(url, requestInit)` with the event's detail."
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"exports": [
|
|
88
|
+
{
|
|
89
|
+
"kind": "js",
|
|
90
|
+
"name": "SuperForm",
|
|
91
|
+
"declaration": {
|
|
92
|
+
"name": "SuperForm",
|
|
93
|
+
"module": "super-form.ts"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"kind": "custom-element-definition",
|
|
98
|
+
"name": "super-form",
|
|
99
|
+
"declaration": {
|
|
100
|
+
"name": "SuperForm",
|
|
101
|
+
"module": "super-form.ts"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<section>
|
|
2
|
+
<super-form id="demo-super-form-external" class="tag-article grid">
|
|
3
|
+
<form action="/api/echo" method="post">
|
|
4
|
+
<label>
|
|
5
|
+
Nickname
|
|
6
|
+
<input type="text" name="nickname" placeholder="John" required>
|
|
7
|
+
</label>
|
|
8
|
+
</form>
|
|
9
|
+
<ul></ul>
|
|
10
|
+
<ul></ul>
|
|
11
|
+
<template id="demo-super-form-item">
|
|
12
|
+
<li class="tag-code"></li>
|
|
13
|
+
</template>
|
|
14
|
+
</super-form>
|
|
15
|
+
<button type="button" command="--submit" commandfor="demo-super-form-external">
|
|
16
|
+
Save (outside the form)
|
|
17
|
+
</button>
|
|
18
|
+
<style>
|
|
19
|
+
#demo-super-form-external-trigger { max-width: none; }
|
|
20
|
+
#demo-super-form-external-trigger super-form {
|
|
21
|
+
&[is-loading] {
|
|
22
|
+
opacity: 0.5;
|
|
23
|
+
border: 1px dashed yellow;
|
|
24
|
+
}
|
|
25
|
+
&[is-success] { border: 1px solid green; }
|
|
26
|
+
&[is-error] {
|
|
27
|
+
border: 1px solid red;
|
|
28
|
+
&::before { content: "An error occurred."; }
|
|
29
|
+
}
|
|
30
|
+
ul:first-of-type::before { content: "Submitted JSON:"; }
|
|
31
|
+
ul:last-of-type::before { content: "Response data:"; }
|
|
32
|
+
li { display: block; }
|
|
33
|
+
}
|
|
34
|
+
</style>
|
|
35
|
+
<quark-sheet>
|
|
36
|
+
super-form[is-success] {
|
|
37
|
+
$res: prop("provision");
|
|
38
|
+
li { content: "#{index}: #{item}"; }
|
|
39
|
+
ul:first-of-type { content: iterate($res.body.json, "#demo-super-form-item"); }
|
|
40
|
+
ul:last-of-type { content: iterate($res, "#demo-super-form-item"); }
|
|
41
|
+
}
|
|
42
|
+
</quark-sheet>
|
|
43
|
+
</section>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<section>
|
|
2
|
+
<super-form>
|
|
3
|
+
<form action="/api/echo" method="post">
|
|
4
|
+
<input type="text" name="name" placeholder="Your name" required>
|
|
5
|
+
<button type="submit">Submit</button>
|
|
6
|
+
</form>
|
|
7
|
+
<h4></h4>
|
|
8
|
+
<span></span>
|
|
9
|
+
</super-form>
|
|
10
|
+
<quark-sheet>
|
|
11
|
+
super-form[is-success] {
|
|
12
|
+
$res: prop("provision");
|
|
13
|
+
h4 { content: "Echoed name: #{$res.body.json.name}"; }
|
|
14
|
+
}
|
|
15
|
+
super-form {
|
|
16
|
+
$res: prop("provision");
|
|
17
|
+
span { content: "HTTP status code: #{$res.status or ""}"; }
|
|
18
|
+
}
|
|
19
|
+
</quark-sheet>
|
|
20
|
+
<style>
|
|
21
|
+
#demo-super-form-simple super-form[is-loading] {
|
|
22
|
+
animation: pulse 1s linear infinite;
|
|
23
|
+
pointer-events: none;
|
|
24
|
+
}
|
|
25
|
+
@keyframes pulse {
|
|
26
|
+
0% { opacity: 0.2; }
|
|
27
|
+
50% { opacity: 0.6; }
|
|
28
|
+
100% { opacity: 0.2; }
|
|
29
|
+
}
|
|
30
|
+
</style>
|
|
31
|
+
</section>
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# super-form
|
|
2
|
+
|
|
3
|
+
Submit AJAX requests with HTML forms. Pair it with Quark to render the response.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
```html
|
|
7
|
+
<section>
|
|
8
|
+
<super-form>
|
|
9
|
+
<form action="/api/echo" method="post">
|
|
10
|
+
<input type="text" name="name" placeholder="Your name" required>
|
|
11
|
+
<button type="submit">Submit</button>
|
|
12
|
+
</form>
|
|
13
|
+
<h4></h4>
|
|
14
|
+
<span></span>
|
|
15
|
+
</super-form>
|
|
16
|
+
<quark-sheet>
|
|
17
|
+
super-form[is-success] {
|
|
18
|
+
$res: prop("provision");
|
|
19
|
+
h4 { content: "Echoed name: #{$res.body.json.name}"; }
|
|
20
|
+
}
|
|
21
|
+
super-form {
|
|
22
|
+
$res: prop("provision");
|
|
23
|
+
span { content: "HTTP status code: #{$res.status or ""}"; }
|
|
24
|
+
}
|
|
25
|
+
</quark-sheet>
|
|
26
|
+
<style>
|
|
27
|
+
#demo-super-form-simple super-form[is-loading] {
|
|
28
|
+
animation: pulse 1s linear infinite;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
}
|
|
31
|
+
@keyframes pulse {
|
|
32
|
+
0% { opacity: 0.2; }
|
|
33
|
+
50% { opacity: 0.6; }
|
|
34
|
+
100% { opacity: 0.2; }
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
37
|
+
</section>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- **Makes AJAX requests** JSON payload is built from each input's `name` and `type` attributes
|
|
44
|
+
- **Progressively enhanced** Doesn't replace the native `form`; it enhances it. Everything you know about `form` and `input` still applies.
|
|
45
|
+
- **Provides data** Use Quark to render the response
|
|
46
|
+
- **Submit command** `--submit` submits programmatically (`<button command="--submit" commandfor="…">`)
|
|
47
|
+
- **Highly configurable** Headers, credentials, redirect, etc
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
`@excom/super-form` v0.1.0
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pnpm add @excom/super-form
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install @excom/super-form
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
yarn add @excom/super-form
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Import
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import "@excom/super-form";
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
Just wrap a regular form. Form fields become a JSON payload via their `name`: dot-separated names nest, and a trailing `[]` collects same-named fields into an array. `input[type]` determines the type conversion.
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<super-form>
|
|
80
|
+
<form action="/api/signup" method="post">
|
|
81
|
+
<input name="isAvailable" type="checkbox"> <!-- -> { isAvailable: true } -->
|
|
82
|
+
<input name="address.city" value="Anytown"> <!-- -> { address: { city: "Anytown" } } -->
|
|
83
|
+
<input name="tags[]" value="smart">
|
|
84
|
+
<input name="tags[]" value="kind"> <!-- -> { tags: ["smart", "kind"] } -->
|
|
85
|
+
<button type="submit">Sign up</button>
|
|
86
|
+
</form>
|
|
87
|
+
</super-form>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Hook the lifecycle state with CSS:
|
|
91
|
+
|
|
92
|
+
```css
|
|
93
|
+
super-form[is-loading] { /* form currently submitting, show loading spinner */ }
|
|
94
|
+
super-form[is-error]::before { content: "An error occurred." }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Or Quark:
|
|
98
|
+
|
|
99
|
+
```quark
|
|
100
|
+
super-form[is-success] {
|
|
101
|
+
$res: prop("provision").body;
|
|
102
|
+
span { content: $res.json.email; }
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### API Reference
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
#### Attributes
|
|
110
|
+
|
|
111
|
+
| Name | Surface | Type | Default | Values | Description | Inherited from |
|
|
112
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
113
|
+
| `form-ref` | option | `string` | `":scope form"` | `<CSS Selector>` | CSS selector for the `<form>` to intercept. The form's `action` / `method` / `enctype` take priority over `api-url` / `api-method` below. Must be a descendant to be heard directly — point elsewhere and invoke the `--submit` command instead. | |
|
|
114
|
+
| `has-body` | option | `boolean` | | | Force a request body even for methods that don't imply one (`GET` / `HEAD`). Already implied for `POST` / `PUT` / `PATCH`. | `@excom/fetchable-element` |
|
|
115
|
+
| `api-url` | option | `string` | `""` | | Endpoint URL. When the request has no body, the JSON payload (from `form-ref` or custom `doFetch()` args) is merged in as query params instead. | `@excom/fetchable-element` |
|
|
116
|
+
| `api-method` | option | `string` | `"GET"` | | HTTP method. Always uppercased before the request is sent. | `@excom/fetchable-element` |
|
|
117
|
+
| `header-accept` | option | `string` | `"application/json"` | | `Accept` request header. | `@excom/fetchable-element` |
|
|
118
|
+
| `header-content-type` | option | `string` | `"application/json"` | | `Content-Type` request header. Dropped entirely when the request has no body. | `@excom/fetchable-element` |
|
|
119
|
+
| `header-cache-control` | option | `string` | | | `Cache-Control` request header. Unset by default (browser default caching applies). | `@excom/fetchable-element` |
|
|
120
|
+
| `fetch-redirect` | option | `string` | | `"follow"` \| `"error"` \| `"manual"` | `RequestInit.redirect` mode. Unset defers to the browser default (`follow`). | `@excom/fetchable-element` |
|
|
121
|
+
| `fetch-credentials` | option | `string` | `"include"` | `"omit"` \| `"same-origin"` \| `"include"` | `RequestInit.credentials` mode. | `@excom/fetchable-element` |
|
|
122
|
+
| `is-loading` | state | `boolean` | | | A request is currently in flight. | `@excom/fetchable-element` |
|
|
123
|
+
| `is-success` | state | `boolean` | | | The most recent request resolved successfully. Mutually exclusive with `is-error`. | `@excom/fetchable-element` |
|
|
124
|
+
| `is-error` | state | `boolean` | | | The most recent request failed (non-2xx status, network error, or a thrown error other than `AbortError`). Fires with the `error` event. | `@excom/fetchable-element` |
|
|
125
|
+
|
|
126
|
+
#### Provision
|
|
127
|
+
|
|
128
|
+
| Name | Type | Description | Inherited from |
|
|
129
|
+
| --- | --- | --- | --- |
|
|
130
|
+
| `provision` | `FetchResponse` (`{ bodyUsed: boolean; headers: [string, string][]; ok: boolean; redirected: boolean; status: number; statusText: string; type: ResponseType; url: string; body: unknown; }`) | Response payload on success, or error payload on failure. Success shape: `{ status, statusText, ok, headers, url, redirected, bodyUsed, type, body }`. Failure shape is either that same response shape (server responded with an error status) or `{ message, stack }` (request never completed). Not reflected as an attribute. | `@excom/fetchable-element` |
|
|
131
|
+
|
|
132
|
+
#### Fires
|
|
133
|
+
|
|
134
|
+
| Name | Type | Description | Inherited from |
|
|
135
|
+
| --- | --- | --- | --- |
|
|
136
|
+
| `super-form-submit` | `SuperFormSubmitEvent` (`CustomEvent & { type: "super-form-submit"; detail: [url: string, requestInit: RequestInit]; bubbles: true; cancelable: true; composed: true }`) | Internal — dispatched whenever a submit is about to run (real `submit` or the `--submit` command). Built by `getFetchArgs()`. Cancelable; default action calls `doFetch()`. | |
|
|
137
|
+
| `super-form-loading` | `FetchableLoadingEvent` (`CustomEvent & { type: "{tag}-loading"; detail: void; bubbles: true; cancelable: true; composed: true }`) | Dispatched immediately before the request is sent. | `@excom/fetchable-element` |
|
|
138
|
+
| `super-form-success` | `FetchableSuccessEvent` (`CustomEvent & { type: "{tag}-success"; detail: { bodyUsed: boolean; headers: [string, string][]; ok: boolean; redirected: boolean; status: number; statusText: string; type: ResponseType; url: string; body: unknown; }; bubbles: true; cancelable: true; composed: true }`) | Dispatched when the request resolves successfully. `event.detail` is the parsed response (see `provision`). | `@excom/fetchable-element` |
|
|
139
|
+
| `super-form-error` | `FetchableErrorEvent` (`CustomEvent & { type: "{tag}-error"; detail: { bodyUsed: boolean; headers: [string, string][]; ok: boolean; redirected: boolean; status: number; statusText: string; type: ResponseType; url: string; body: unknown; } \| { message: string; stack?: string }; bubbles: true; cancelable: true; composed: true }`) | Dispatched when the request fails — non-2xx status, network error, or a thrown error. `event.detail` is the error payload (see `provision`). Not dispatched for aborted requests. | `@excom/fetchable-element` |
|
|
140
|
+
|
|
141
|
+
#### Listens for
|
|
142
|
+
|
|
143
|
+
| Name | Type | Description |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| `submit` | `SuperFormNativeSubmitEvent` (`SubmitEvent & { type: "submit"; bubbles: true; cancelable: true; composed: false; }`) | The default action of the `<form>` matched by `form-ref` (or any descendant `<form>`); prevented, then converted into `super-form-submit`. |
|
|
146
|
+
|
|
147
|
+
#### Commands
|
|
148
|
+
|
|
149
|
+
| Command | Action |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| `--submit` | Submits programmatically (`<button command="--submit" commandfor="…">`) — the only option when `form-ref` points to a form that isn't a descendant, since this element can't hear its `submit` event directly. |
|
|
152
|
+
|
|
153
|
+
#### Default actions
|
|
154
|
+
|
|
155
|
+
| Event | Default behavior (unless preventDefault() is called) |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `super-form-submit` | Calls `doFetch(url, requestInit)` with the event's detail. |
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
### Examples
|
|
162
|
+
|
|
163
|
+
#### Comprehensive
|
|
164
|
+
|
|
165
|
+
This example shows loading state, error state, rendering, and triggering from outside the form.
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<section>
|
|
170
|
+
<super-form id="demo-super-form-external" class="tag-article grid">
|
|
171
|
+
<form action="/api/echo" method="post">
|
|
172
|
+
<label>
|
|
173
|
+
Nickname
|
|
174
|
+
<input type="text" name="nickname" placeholder="John" required>
|
|
175
|
+
</label>
|
|
176
|
+
</form>
|
|
177
|
+
<ul></ul>
|
|
178
|
+
<ul></ul>
|
|
179
|
+
<template id="demo-super-form-item">
|
|
180
|
+
<li class="tag-code"></li>
|
|
181
|
+
</template>
|
|
182
|
+
</super-form>
|
|
183
|
+
<button type="button" command="--submit" commandfor="demo-super-form-external">
|
|
184
|
+
Save (outside the form)
|
|
185
|
+
</button>
|
|
186
|
+
<style>
|
|
187
|
+
#demo-super-form-external-trigger { max-width: none; }
|
|
188
|
+
#demo-super-form-external-trigger super-form {
|
|
189
|
+
&[is-loading] {
|
|
190
|
+
opacity: 0.5;
|
|
191
|
+
border: 1px dashed yellow;
|
|
192
|
+
}
|
|
193
|
+
&[is-success] { border: 1px solid green; }
|
|
194
|
+
&[is-error] {
|
|
195
|
+
border: 1px solid red;
|
|
196
|
+
&::before { content: "An error occurred."; }
|
|
197
|
+
}
|
|
198
|
+
ul:first-of-type::before { content: "Submitted JSON:"; }
|
|
199
|
+
ul:last-of-type::before { content: "Response data:"; }
|
|
200
|
+
li { display: block; }
|
|
201
|
+
}
|
|
202
|
+
</style>
|
|
203
|
+
<quark-sheet>
|
|
204
|
+
super-form[is-success] {
|
|
205
|
+
$res: prop("provision");
|
|
206
|
+
li { content: "#{index}: #{item}"; }
|
|
207
|
+
ul:first-of-type { content: iterate($res.body.json, "#demo-super-form-item"); }
|
|
208
|
+
ul:last-of-type { content: iterate($res, "#demo-super-form-item"); }
|
|
209
|
+
}
|
|
210
|
+
</quark-sheet>
|
|
211
|
+
</section>
|
|
212
|
+
```
|