@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-input

A lightweight element that wraps and upgrades the native <input> element.

Features

  • Live text formatting e.g. phone numbers, dates, SSNs
  • Custom, native validity messages uses the browser's built-in validation UI to show your message
  • Auto-labeling it stitches a sibling <label> to the <input> via id/for
  • Progressively enhanced Does not replace the native input; it enhances it. Everything you know about <input> still applies.
  • Range slider ships with upgraded slider styling and functionality

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-input@0.1.5/dist/index.umd.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@excom/super-input@0.1.5/dist/index.css">
npm install @excom/super-input
HTML Imports JS / CSS Imports
<!-- import path to `node_modules` will depend on your build setup -->
<script type="module" src="/node_modules/@excom/super-input"></script>
<link rel="stylesheet" href="/node_modules/@excom/super-input">
import "@excom/super-input";
@import "@excom/super-input/index.css";
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.bundle.min.css
  • default: ./dist/index.bundle.min.css
./index.css
  • default: ./dist/index.css
./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.css
  • default: ./dist/index.min.css
./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-input
  • types: ./dist/super-input.d.ts
  • import: ./dist/super-input.js
  • default: ./dist/super-input.js
./super-input.js
  • types: ./dist/super-input.d.ts
  • import: ./dist/super-input.js
  • default: ./dist/super-input.js
./super-input.min
  • types: ./dist/super-input.d.ts
  • import: ./dist/super-input.min.js
  • default: ./dist/super-input.min.js
./super-input.min.js
  • types: ./dist/super-input.d.ts
  • import: ./dist/super-input.min.js
  • default: ./dist/super-input.min.js
./super-input.umd.min
  • types: ./dist/super-input.d.ts
  • default: ./dist/super-input.umd.min.js
./super-input.umd.min.js
  • types: ./dist/super-input.d.ts
  • default: ./dist/super-input.umd.min.js

Usage

Wrap a native <input> and optionally a <label>. Nothing else is required.

API Reference

super-input

Attributes (5) Role — option is configurable, state is managed by the element (read-only), or hybrid which is both. Role Attribute Type Values Prop Default option auto-label
When set, generates or uses an existing id on the <input> and associates the <label>. autoLabel
boolean false
option invalid-message
If set, this message is installed via setCustomValidity() when the native invalid event fires, and cleared on the next keydown. Makes built-in HTML validation surface a custom message. invalidMessage
string null
option reflect-value
When set, the current input value is mirrored to the current-value attribute so CSS and selectors can respond to it. Off by default because reflecting every keystroke is not free. reflectValue
boolean false
option text-format
Pattern defining visible formatting. Use x for any digit and any other character as a literal. Examples: (xxx) xxx-xxxx, xx/xx/xxxx, xxx-xx-xxxx. When set, the input value is live-formatted on every keystroke. textFormat
string null
state current-value
Mirrors the wrapped <input>'s value when reflect-value is set. Read-only from the app's point of view — writing it does not change the input. currentValue
string null
Provision (0) 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
Events (0)
Dispatches — events that super-input 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.
Listeners — events that super-input listens for which will trigger subsequent actions. Listener Type Action
Commands — verbs super-input accepts as a command event (HTML Command API): from a <button command commandfor>, an <event-handler command-name>, or a CommandEvent. Command Action
Recognized Elements (2) Child or descendant elements are recognized by super-input and relevant to its functionality. Element Relationship Required input
Required native <input> to enhance.
descendant true
label
Optional native <label>. When auto-label is set, its for attribute is linked to the input's id.
descendant false
Styles (26)
Classes — optional classes that change the appearance of super-input. Class Description .raised Raised / floating-label text field appearance. Apply as class="raised" on <super-input> wrapping a text-like <input>.
Variables — public CSS variables for theming super-input. 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 --super-input-accent <color> var(--super-input-primary, blue) --super-input-bg <color> var(--v-form-element-background-color, transparent) --super-input-border-color <color> var( --v-form-element-border-color, currentColor ) --super-input-border-radius <length> var(--v-border-radius, 4px) --super-input-border-width <length> var(--v-border-width, 1px) --super-input-color <color> var(--v-color, inherit) --super-input-focus-color <color> var( --v-form-element-focus-color, var(--super-input-border-color) ) --super-input-font-size <length> 16px --super-input-font-weight <number> | <integer> 700 --super-input-height <length> calc(var(--super-input-thumb-size) + 36px) --super-input-label-height <length> calc( var(--v-line-height, 1.5) * var(--super-input-font-size) ) --super-input-label-margin-bottom <length> calc(21px * 0.375) --super-input-max <number> 100 --super-input-min <number> 0 --super-input-primary <color> var(--v-primary, blue) --super-input-thumb-color <color> var( --v-range-thumb-color, var(--super-input-bg) ) --super-input-thumb-size <length> 44px --super-input-track-height <length> 2px --super-input-transition-duration <time> var(--v-transition-duration-fast, 0.15s) --super-input-transition-ease * var(--v-transition-ease-out, ease-out) --super-input-value <number> 50 --super-input-width <length> | <percentage> 100%
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 :--super-input element super-input, .tag-super-input :--super-input--has-current-value state [current-value], [data-current-value] :--super-input--has-reflect-value state [reflect-value], [data-reflect-value]
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

Formatting input as the user types

Set text-format to a template using x as a character placeholder.

Common templates:

  • Phone (US): (xxx) xxx-xxxx
  • Date: xx/xx/xxxx
  • SSN: xxx-xx-xxxx

The wrapped <input> sees the formatted value. Pair with pattern for validation.

Custom validity messages

Set invalid-message and the browser's native validation UI will surface it when the input fails. Use the native pattern, required, min, max, etc for validation. Try submitting the form in the demo below with an invalid phone number.

The message is installed via setCustomValidity() on the invalid event and cleared on the next keydown, so the input stops being marked invalid as soon as the user tries again.

Using reflect-value

The reflect-value attribute has two primary uses:

  • Is required for animating raised labels without an input placeholder attribute
  • Allows you to hook into it to run your own behaviors

Here's an example with a raising label that will display an error if the user has not entered an email with an @ or . characters.

Range slider

<super-input> ships with advanced slider CSS. Add [type="range"] to the input. [reflect-value] is required for this to work natively in Chromium browsers. Safari and Firefox will need some help via Quark or JS until they support the CSS type() function. You must also set the min/max CSS variables to match the min/max on the input.

Be cautious using this feature, as it aesthetically relies on non-standard pseudo elements for the time being.

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.