@use "/shell" as *; @use "quark:list" as list; #release-notice { /* opens once: sheet re-runs restart a delay, so gate it on the fact it writes */ &:not([data-did-open]) { @delay 7000 { is-open: ""; data-did-open: ""; } } /* prevent clicks from bubbling to the sheet */ @on mouseup, click (stop-propagation); } provider-fetch[api-url*="package-metas/index.json"][is-success] { $package-indices: prop("provision").body; /* `excom.navGroup` moves a package out of its type's list, into that group */ $ungrouped: list.reject($package-indices.packages, "navGroup"); .package-links { [bind-elements] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "kit-element")); } [bind-element-bases] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "element-base")); } [bind-tools] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "tool")); } /* the standalone libraries: same rows, rendered flat (shell.css) */ [bind-libraries] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "library")); } [bind-library-group] ul:not(ul ul) { content: iterate(list.filter($package-indices.packages, "navGroup", "libraries")); } ul:not(ul ul) > li { $pkg: item.shortName; > spa-a { route-href: "/packages/#{item.shortName}"; } } /* one `content` rule per link: a second one would rewrite the first's text on every pass */ ul:not(ul ul, [bind-libraries] ul) > li > spa-a { content: item.shortName; } [bind-libraries] ul:not(ul ul) > li > spa-a { content: displayName(item.shortName); } /* packages whose docs span several pages: one collapsible group per section */ [bind-sections] { content: iterate(item.docSections); summary { content: item.title; } details > ul { content: iterate(item.docs); spa-a { route-href: "/packages/#{$pkg}/#{item.name}"; content: item.title; } } details:not(:has(spa-a[is-active])) { open: none; } } } /* page titles: the page, then the site. The docs home keeps the title in the document head; the other routes carry theirs in markup */ spa-route[is-active] { /* a site guide */ &[route-href$=":name"] { $title-guide: list.find($package-indices.docs, "name", $route.params.name); document-title: "#{$title-guide.title or $route.params.name} · Nucleus · docs"; } /* a package README */ &[route-href$=":packageName"] { document-title: "#{displayName($route.params.packageName)} · Nucleus · docs"; } /* one page of a package's docs */ &[route-href$=":docName"] { $title-package: list.find($package-indices.packages, "shortName", $route.params.packageName); document-title: "#{docTitle($title-package, $route.params.docName)} · #{displayName($route.params.packageName)} · Nucleus · docs"; } } /* the page's markdown file, for the footer link: the docs home and each guide the index lists, and a package's page whose index entry says `markdown` (build-docs-index sets it; the dev server's index has none, so no link there). siteDocHref and the path check say which URL is the page's own: the 404 and a package's doc pages have none */ > spa-manager[active-url] { $page-path: attr("active-url").split("#").at(0).split("?").at(0); $page-name: if($page-path == SITE_HOME: SITE_HOME_DOC; else: $page-path.split("/").at(-1)); $page-guide: list.find($package-indices.docs, "name", $page-name); $page-package: list.find($package-indices.packages, "shortName", $page-name); $page-markdown: if($page-guide and siteDocHref($page-guide.name) == $page-path: "/docs/#{$page-guide.name}.md"; $page-package.markdown and "#{SITE_BASE}/packages/#{$page-package.shortName}" == $page-path: "/#{$page-package.shortName}.md"); footer [bind-page-markdown] { href: $page-markdown; content: ternary($page-markdown, "Markdown version of this page"); } } } spa-route { $route: prop("provision"); /* paramless routes name their guide in markup (the docs home, `/`); /docs/:name gets it from params */ $route-doc-name: attr("data-doc-name"); } /* the desktop aside and the mobile sheet stamp the same nav template */ [data-site-nav] { details:has(spa-a[is-active]) { /* not kosher */ open: ""; } spa-a[is-active] { aria-current: ""; } spa-a:not([is-active]) { aria-current: none; } } /* mobile sheet: Escape / back gesture while open; a tapped link closes it */ #site-menu { &[is-open] dismiss-watcher { is-active: ""; } &:not([is-open]) dismiss-watcher { is-active: none; } @on click (target: "spa-a") { is-open: none; } } #site-menu-gesture { &:has(> #site-menu[is-open]) { progress-offset: 1; } &:not(:has(> #site-menu[is-open])) { progress-offset: 0; } @on gesture-handler-start { #site-menu { is-scrubbing: ""; } } @on gesture-handler-end { #site-menu { is-open: event.detail.snap == 1; is-scrubbing: none; } } } main { @on copy-source (handle: copySource); } #search-dialog[open] { > include-content { is-active: ""; /* for some reason this is necessary on first render */ @on include-content-did-render (handle: focusInput); } input[type="search"] { /* works on all subsequent opens */ autofocus: ""; } } #search-dialog:not([open]) input[type="search"] { autofocus: none; }

super-form

Submit AJAX requests with HTML forms. Pair it with Quark to render the response.

Features

  • Makes AJAX requests JSON payload is built from each input's name and type attributes
  • Progressively enhanced Doesn't replace the native form; it enhances it. Everything you know about form and input still applies.
  • Provides data Use Quark to render the response
  • Submit command --submit submits programmatically (<button command="--submit" commandfor="…">)
  • Highly configurable Headers, credentials, redirect, etc

Installation

This package is available in the NucleusKit. Or it can be used by itself:

CDN Package Manager
<script src="https://unpkg.com/@excom/kit-utils@0.3.0/dist/index.umd.min.js"></script>
<script src="https://unpkg.com/@excom/neutron@0.2.0/dist/index.umd.min.js"></script>
<script src="https://unpkg.com/@excom/super-form@0.1.8/dist/index.umd.min.js"></script>
npm install @excom/super-form
HTML Imports JS / CSS Imports
<!-- import path to `node_modules` will depend on your build setup -->
<script type="module" src="/node_modules/@excom/super-form"></script>
<link rel="stylesheet" href="/node_modules/@excom/super-form">
import "@excom/super-form";
Peer dependencies (0)

Packages a consumer must install alongside this one. Workspace deps are bundled.

Package Version
View Dist Files

All exports:

.
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./index
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./index.js
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./index.min
  • types: ./dist/index.d.ts
  • import: ./dist/index.min.js
  • default: ./dist/index.min.js
./index.min.js
  • types: ./dist/index.d.ts
  • import: ./dist/index.min.js
  • default: ./dist/index.min.js
./index.umd.min
  • types: ./dist/index.d.ts
  • default: ./dist/index.umd.min.js
./index.umd.min.js
  • types: ./dist/index.d.ts
  • default: ./dist/index.umd.min.js
./super-form
  • types: ./dist/super-form.d.ts
  • import: ./dist/super-form.js
  • default: ./dist/super-form.js
./super-form.js
  • types: ./dist/super-form.d.ts
  • import: ./dist/super-form.js
  • default: ./dist/super-form.js
./super-form.min
  • types: ./dist/super-form.d.ts
  • import: ./dist/super-form.min.js
  • default: ./dist/super-form.min.js
./super-form.min.js
  • types: ./dist/super-form.d.ts
  • import: ./dist/super-form.min.js
  • default: ./dist/super-form.min.js
./super-form.umd.min
  • types: ./dist/super-form.d.ts
  • default: ./dist/super-form.umd.min.js
./super-form.umd.min.js
  • types: ./dist/super-form.d.ts
  • default: ./dist/super-form.umd.min.js

Usage

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.

<super-form>
  <form action="/api/signup" method="post">
    <input name="isAvailable" type="checkbox"> <!-- -> { isAvailable: true } -->
    <input name="address.city" value="Anytown"> <!-- -> { address: { city: "Anytown" } } -->
    <input name="tags[]" value="smart">
    <input name="tags[]" value="kind"> <!-- -> { tags: ["smart", "kind"] } -->
    <button type="submit">Sign up</button>
  </form>
</super-form>

Hook the lifecycle state with CSS:

super-form[is-loading] { /* form currently submitting, show loading spinner */ }
super-form[is-error]::before { content: "An error occurred." }

did-load is set on the first success, kept through a resubmit (where is-loading replaces is-success) and cleared on an error: gate content on [did-load] to keep it on screen during a resubmit.

Or Quark:

super-form[is-success] {
  $res: prop("provision").body;
  span { content: $res.json.email; }
}

API Reference

super-form

Attributes (13) Role — option is configurable, state is managed by the element (read-only), or hybrid which is both. Role Attribute Type Values Prop Default option api-method
HTTP method. Always uppercased before the request is sent. apiMethod fetchable-element
string "GET"
option api-url
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. apiUrl fetchable-element
string ""
option fetch-credentials
RequestInit.credentials mode. fetchCredentials fetchable-element
string "omit" | "same-origin" | "include" "include"
option fetch-redirect
RequestInit.redirect mode. Unset defers to the browser default (follow). fetchRedirect fetchable-element
string "follow" | "error" | "manual" null
option form-ref
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. formRef
string <CSS Selector> ":scope form"
option form-ref
CSS selector for a <form> to source the request from — its action (URL), method, enctype (Content-Type), and field values (as the JSON payload) all take priority over the matching attributes below. Omit to build the request entirely from attributes / custom doFetch() args. formRef fetchable-element
string <CSS Selector> null
option has-body
Force a request body even for methods that don't imply one (GET / HEAD). Already implied for POST / PUT / PATCH. hasBody fetchable-element
boolean false
option header-accept
Accept request header. headerAccept fetchable-element
string "application/json"
option header-cache-control
Cache-Control request header. Unset by default (browser default caching applies). headerCacheControl fetchable-element
string null
option header-content-type
Content-Type request header. Dropped entirely when the request has no body. headerContentType fetchable-element
string "application/json"
state is-error
The most recent request failed (status 400 or above, network error, or a thrown error other than AbortError). Fires with the error event. isError fetchable-element
boolean false
state is-loading
A request is currently in flight. isLoading fetchable-element
boolean false
state is-success
The most recent request resolved successfully. Mutually exclusive with is-loading and is-error. isSuccess fetchable-element
boolean false
Provision (1) This provision property lives on the DOM node. Read / watch it with Quark’s prop("provision"), or listen for the neutron-provision event from app JS. Property Type provision
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. provision fetchable-element
FetchResponse
Events (6)
Dispatches — events that super-form fires and their default actions. Dispatch Default Action
A "default action" is subsequent logic executed by the element if e.preventDefault() is not synchronously called on the event.
Name super-form-error
Dispatched when the request fails — status 400 or above, network error, or a thrown error. event.detail is the error payload (see provision). Not dispatched for aborted requests. fetchable-element

Type FetchableErrorEvent
Name super-form-loading
Dispatched immediately before the request is sent. fetchable-element

Type FetchableLoadingEvent
Name super-form-submit
Internal — dispatched whenever a submit is about to run (real submit or the --submit command). Built by getFetchArgs(). Cancelable; default action calls doFetch().

Type SuperFormSubmitEvent
Calls doFetch([url, requestInit]) with the event's detail.
Name super-form-success
Dispatched when the request resolves successfully. event.detail is the parsed response (see provision). fetchable-element

Type FetchableSuccessEvent
Listeners — events that super-form listens for which will trigger subsequent actions. Listener Type Action submit SuperFormNativeSubmitEvent
The default action of the <form> matched by form-ref (or any descendant <form>); prevented, then converted into super-form-submit.
Commands — verbs super-form accepts as a command event (HTML Command API): from a <button command commandfor>, an <event-handler command-name>, or a CommandEvent. Command Action --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.
Recognized Elements (0) Child or descendant elements are recognized by super-form and relevant to its functionality. Element Relationship Required
Styles (0)
Classes — optional classes that change the appearance of super-form. Class Description
Variables — public CSS variables for theming super-form. Opt-out:
If you wish to opt-out of these styles on a case-to-case basis, use property all: revert-layer. Valence.css ships this as .unstyled and .unstyled-all for the entire tree.
Variable Syntax Default
Aliases — @custom-selector synonyms so custom elements can share semantic styles. Element aliases match the host tag / .tag-* class; state aliases match attributes (nested under the element alias). Alias Kind Matches
Release notes (1)

0.1.1

  • Ship only dist (and declared extras) in the npm tarball; drop build logs, tests and sources
View Source

Examples

Comprehensive

This example shows loading state, error state, rendering, and triggering from outside the form.

Beta. The Nucleus Stack is in beta for a few weeks until features are stabilized and optimized.

Thanks — we'll email you when it ships.

Something went wrong. Please try again.