@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; }

detect-browser

Drop-in tag provides browser, OS, device, and language metadata — target Safari, standalone mode, mobile devices, etc. Helpful in creating device-specific UX.

Features

  • Attribute metadata Set once on connect for CSS / Quark targeting
  • Standalone / PWA detection is-standalone reflects installed mode
  • Language detection language-id reflects system-selected language
  • Device class device-type (mobile / desktop) for coarse targeting
  • Full metadata .provision exposes user agent, languages, hardware hints

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

Usage

Drop it anywhere and select on its attributes with plain CSS.

<detect-browser></detect-browser>
html:has(detect-browser[browser-name="safari"]) .safari-only {
  display: block;
}

Live touch / pointer detection is detect-media media-query="(pointer: coarse)" — this element reports what the user agent says once, not what the input is now.

API Reference

detect-browser

Attributes (11) Role — option is configurable, state is managed by the element (read-only), or hybrid which is both. Role Attribute Type Values Prop Default state browser-engine
Rendering engine. browserEngine
string "blink" | "gecko" | "webkit" | "trident" null
state browser-name
Detected browser family. browserName
string "chrome" | "firefox" | "safari" | "edge" | "opera" | internet explorer null
state browser-vendor
navigator.vendor, lowercased. browserVendor
string null
state browser-version
Browser version, as reported by the user agent string. browserVersion
string null
state cookies-enabled
Present when navigator.cookieEnabled is true. cookiesEnabled
boolean false
state device-type
Coarse device class inferred from the user agent string. deviceType
string "mobile" | "desktop" null
state do-not-track
navigator.doNotTrack, as reported by the browser. doNotTrack
string null
state is-standalone
Present when running in standalone / installed PWA mode. isStandalone
boolean false
state language-id
navigator.language, lowercased. languageId
string null
state operating-platform
navigator.userAgentData.platform (falls back to navigator.platform), lowercased. operatingPlatform
string null
state operating-system
Detected OS and, where available, version. operatingSystem
string windows 10+ | windows 8.1 | windows 8 | windows 7 | macos <version> | "android" | "ios" | "linux" null
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
Every field above, plus values with no CSS-friendly attribute form: full user agent string, navigator.languages, hardware concurrency, and device memory (GB, where supported). Not reflected as an attribute. provision
BrowserInfo
Events (0)
Dispatches — events that detect-browser 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 detect-browser listens for which will trigger subsequent actions. Listener Type Action
Commands — verbs detect-browser 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 (0) Child or descendant elements are recognized by detect-browser and relevant to its functionality. Element Relationship Required
Styles (0)
Classes — optional classes that change the appearance of detect-browser. Class Description
Variables — public CSS variables for theming detect-browser. 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

Language

Showing a greeting in your system language (Only Spanish or English for this example). language-id mirrors navigator.language (lowercased). Spanish prefixes show Hola; everything else shows Hello. Change the browser language (or override it in DevTools) and reload to flip it.

Safari-only install hint

iOS Safari has no install prompt, so the sheet renders "Add to Home Screen" steps there, and not once the app is installed. Spoof an iOS Safari user agent in DevTools and reload to trigger it.

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.