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

content-carousel

Rotate slides on autopilot or on click — galleries, hero banners, walkthroughs.

Features

  • Auto-play Rotate on a timer via auto-play
  • Manual nav --back / --next commands from plain buttons; rel="prev" / rel="next" positions them
  • Slide or fade Choose the transition with slide-animation: slide / fade / track
  • Swipeable slide-animation="track" wrapped in gesture-handler: drag and flick between slides
  • Pauses itself Manual navigation stops auto-play automatically
  • Bindable position .provision is { index, count, lastMove } — a progress readout is one Quark rule on prop("provision")

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

Usage

Wrap <content-carousel-slide> elements in a <content-carousel>. Mark one is-active, or leave it unset to default to the first.

<content-carousel auto-play="5">
  <content-carousel-slide is-active>One</content-carousel-slide>
  <content-carousel-slide>Two</content-carousel-slide>
  <content-carousel-slide>Three</content-carousel-slide>
</content-carousel>

.provision is { index, count, lastMove } — set on connect and after every slide change (a command, auto-play, a slide added / removed, is-active written on a slide), counting only this carousel's own slides. A "2 / 3" readout:

content-carousel {
  $slide: prop("provision").index + 1;
  $count: prop("provision").count;
  [bind-progress] { content: "#{$slide} / #{$count}"; }
}

Nothing animates until the first move: last-move is unset until then, so an initial is-active, in markup or written by the app, appears in place. slide-animation="track" renders only the active slide and its two DOM neighbours, so slides must be siblings (a display: contents wrapper around them is fine) and, with three or more slides, a wrap — --next from the last, --back from the first — cuts to the new slide instead of sliding; put it inside gesture-handler to drag it.

API Reference

content-carousel

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-play
Seconds between automatic slide advances. Unset disables auto-play. autoPlay
number null
option is-scrubbing
Set when the element is being dragged. Used by CSS only to disable transition. isScrubbing
boolean false
option slide-animation
Transition used when the active slide changes. slideAnimation
string "slide" | "fade" | "track" "slide"
hybrid auto-play-stopped
Set automatically after manual navigation, or when the element leaves the document (a DOM move keeps auto-play running), to pause auto-play. Set it directly to pause / resume auto-play programmatically. autoPlayStopped
boolean false
state last-move
Direction of the most recent slide change — drives the CSS animation direction. is-active moved from one slide to another counts as forward / back by position. Unset until the first move, so the initial slide appears without animating in. lastMove
string "forward" | "back" 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
Position: { index, count, lastMove } — the active slide's index among this carousel's own slides, how many there are, and the direction of the last move. Set on connect and after every change, including slides added / removed and is-active written on a slide. Not reflected as an attribute. provision
ContentCarouselProvision
Events (3)
Dispatches — events that content-carousel 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 content-carousel-slide-changed
After any slide change: a command, auto-play, or is-active moved from one slide to another (a swipe).

Type ContentCarouselSlideChangedEvent
Listeners — events that content-carousel listens for which will trigger subsequent actions. Listener Type Action
Commands — verbs content-carousel accepts as a command event (HTML Command API): from a <button command commandfor>, an <event-handler command-name>, or a CommandEvent. Command Action --back Shows the previous slide (wraps to last). Stops auto-play. --next Shows the next slide (wraps to first). Stops auto-play.
Recognized Elements (1) Child or descendant elements are recognized by content-carousel and relevant to its functionality. Element Relationship Required content-carousel-slide
Slides to rotate through. The first, or whichever has is-active, is shown initially.
descendant true
Styles (15)
Classes — optional classes that change the appearance of content-carousel. Class Description .tag-content-carousel-next Positions a direct child with rel="next" (or .tag-content-carousel-next) as the "next slide" nav control. .tag-content-carousel-prev Positions a direct child with rel="prev" (or .tag-content-carousel-prev) as the "previous slide" nav control.
Variables — public CSS variables for theming content-carousel. 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 --content-carousel-button-size <length> 25px --content-carousel-progress <number> 0 --content-carousel-slide-animation-duration <time> 0.25s --content-carousel-transition-ease <easing-function> ease-out
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 :--content-carousel element content-carousel, .tag-content-carousel :--content-carousel--is-scrubbing state [is-scrubbing], [data-scrubbing] :--content-carousel--last-move state [last-move], [data-last-move] :--content-carousel--last-move-back state [last-move="back"], [data-last-move="back"] :--content-carousel--last-move-forward state [last-move="forward"], [data-last-move="forward"] :--content-carousel--slide-animation state [slide-animation], [data-slide-animation] :--content-carousel--slide-animation-fade state [slide-animation="fade"], [data-slide-animation="fade"] :--content-carousel--slide-animation-slide state [slide-animation="slide"], [data-slide-animation="slide"] :--content-carousel--slide-animation-track state [slide-animation="track"], [data-slide-animation="track"]

content-carousel-slide

Attributes (1) Role — option is configurable, state is managed by the element (read-only), or hybrid which is both. Role Attribute Type Values Prop Default hybrid is-active
Marks this as the currently shown slide. The parent <content-carousel> keeps exactly one slide active. Write it directly (a swipe) and the carousel's provision, last-move and slide-changed event follow. isActive
boolean false
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 content-carousel-slide 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 content-carousel-slide listens for which will trigger subsequent actions. Listener Type Action
Commands — verbs content-carousel-slide 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 content-carousel-slide and relevant to its functionality. Element Relationship Required
Styles (2)
Classes — optional classes that change the appearance of content-carousel-slide. Class Description
Variables — public CSS variables for theming content-carousel-slide. 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 :--content-carousel-slide element content-carousel-slide, .tag-content-carousel-slide :--content-carousel-slide--is-active state [is-active], [aria-current]
Release notes (3)

0.1.3

  • Track mode hides every slide beyond the active slide's neighbours and no longer animates the first slide into place

0.1.2

  • Auto-play tests poll for the advanced slide instead of sleeping a fixed 80 ms (flaked on loaded CI runners); no runtime change
  • Fix an issue where provision did not follow slides added or removed, and last-move and content-carousel-slide-changed did not follow is-active moving from one slide to another (a swipe): each change sets one provision, and a carousel moved in the DOM no longer sets it again
  • Update the internal autoPlayIntervalId property to _autoPlayIntervalId, as its other private properties are named; it was never an attribute, so markup is unaffected

0.1.1

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

Examples

Manual navigation

Invoke --back / --next from a <button command commandfor>. A direct child with rel="prev" / rel="next" (or .tag-content-carousel-prev / .tag-content-carousel-next) is positioned as the nav control for you. If you want swipeable slides, you must use the carousel in conjunction with the . A demo exists on that page.

Fade transition

slide-animation="fade" crossfades instead of sliding.

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.