Settings

Sidebar
Display
Profile Panel
👤

John Doe

Administrator
  • 📊 Dashboard
  • 📝 Forms
  • 📋 Tables
Commands
🚀
Go to Page
Navigate to a page
/go
🌓
Toggle Theme
Switch between light and dark mode
/theme
📐
Toggle Sidebar
Show or hide the sidebar
/sidebar
⚙️
Open Settings
Open the settings panel
/settings
Search In
📄
Pages
Search pages
:p
🧩
Components
Search component pages
:c

Range Group

A compact multi-range filter. One toggle summarises several numeric range filters inline (Age / Salary / Children) and expands into a floating panel with one slider per dimension — instead of spreading three separate sliders across a filter bar. Each row is dual-thumb (min–max) or single-thumb (a / threshold), configured per-row.

In a filter bar

The range group drops into a .pa-filter-card__filters row as a single control standing in for three sliders. Open it, drag, and Apply.

Emitted values

The group dispatches pa-range-group:change live and pa-range-group:apply / :reset on the buttons. Payload is keyed by data-key; a bound at its extent reports null (i.e. “Any”).

// interact with the filter above…

Single-thumb thresholds

Set mode: 'single' for a one-handle threshold. bound: 'gte' reads as “value+”, bound: 'lte' as “≤ value”.

Bound values

{
  "rating": {
    "value": 4,
    "bound": "gte"
  },
  "distance": {
    "value": 20,
    "bound": "lte"
  }
}

Handle shapes

Add a handleShape to any row to restyle its handles. Purely cosmetic (no JS), so you can mix shapes per row. Open the filter to compare — circle (default), rect, bar, arrow, and needle (a downward triangle balancing its point on the track).

Bound values

{
  "circle": {
    "min": 20,
    "max": 70
  },
  "rect": {
    "min": 20,
    "max": 70
  },
  "bar": {
    "min": 20,
    "max": 70
  },
  "arrow": {
    "min": 20,
    "max": 70
  },
  "needle": {
    "min": 20,
    "max": 70
  }
}

Ticks & click-to-seek

Add ticks (major interval) and optionally ticksMinor to draw tick marks along the track; add tickLabels to print the major values beneath. Marks sit behind the track so only their ends show — major ticks reach further out than minor. On every row the track is click-to-seek: press anywhere off the handles and the nearest thumb jumps there and keeps following the pointer. Add snapTicks to make the thumbs settle on the nearest tick instead of the (possibly finer) step grid — drag the Level row and it snaps to 0, 10, 20…

Bound values

{
  "age": {
    "min": 25,
    "max": 60
  },
  "score": {
    "min": 40,
    "max": 90
  },
  "level": {
    "min": 20,
    "max": 80
  },
  "rating": {
    "value": 3,
    "bound": "gte"
  }
}
<div class="pa-range" data-range data-key="age"
     data-min="0" data-max="80"
     data-ticks="20" data-ticks-minor="10" data-tick-labels>

Theming with --pa-range-* tokens

Every colour and key dimension is a runtime token that falls back to the shared cascade, so a theme or a per-instance panelStyle can retint/resize a slider with no recompile. This group overrides the fill, thumb, track thickness, and handle size — set on the __panel (it reparents to <body> when open, so tokens on the group root wouldn't reach the sliders).

Bound values

{
  "budget": {
    "min": 1000,
    "max": 3500
  },
  "guests": {
    "value": 4,
    "bound": "gte"
  }
}
<div class="pa-range-group__panel" data-range-group-panel
     style="--pa-range-fill: #8b5cf6;
            --pa-range-thumb-border: #8b5cf6;
            --pa-range-track-height: 0.8rem;
            --pa-range-thumb-size: 2rem;">

Sync with the URL (qsKey)

Set qsKey and the group reads its initial state from that URL param and writes back as you drag — bookmarkable, shareable, and back/forward-aware. The whole group packs into one param via the built-in codec (override with codec). Only user changes are written, so the URL starts empty; drag a slider and watch the address bar update. This demo also sets debounce={200}, so sliding back and forth coalesces into a single write when you settle — the URL (and any fetch/filter you wire to it) never thrashes.

Live URL

(no query yet — drag a slider)

Bound values (bind:values)

{}

This docs site is SvelteKit, so it passes a ~10-line QsAdapter built on replaceState. A plain Vite / History app needs no adapter at all — just qsKey. For a hash SPA (@keenmate/svelte-spa-router), use the createSpaRouterAdapter factory. All three are below.

Usage

// 1 · Plain Vite / History app — zero config (default History adapter).
//     debounce coalesces rapid drags into one write (values / onchange / URL):
<RangeGroup rows={filterRows} qsKey="filters" debounce={200} />

// 2 · SvelteKit — a tiny adapter: read from location, write via replaceState.
// subscribe MUST fire on external changes only (popstate) — NOT the page store,
// or a live write would remount the open panel.
import { replaceState } from '$app/navigation';
import type { QsAdapter } from '@keenmate/svelte-pure-admin';

const sveltekit: QsAdapter = {
  read:  (k) => new URLSearchParams(location.search).get(k),
  write: (k, v) => {
    const url = new URL(location.href);
    if (v == null || v === '') url.searchParams.delete(k);
    else url.searchParams.set(k, v);
    replaceState(url.pathname + url.search, {});
  },
  subscribe: (cb) => {
    addEventListener('popstate', cb);
    return () => removeEventListener('popstate', cb);
  }
};
<RangeGroup rows={filterRows} qsKey="filters" qsAdapter={sveltekit} />

// 3 · svelte-spa-router (hash SPA) — ready-made factory:
import { querystring, location, replace } from '@keenmate/svelte-spa-router';
import { createSpaRouterAdapter }
  from '@keenmate/svelte-pure-admin/adapters/svelte-spa-router';

const router = createSpaRouterAdapter({ querystring, location, replace });
<RangeGroup rows={filterRows} qsKey="filters" qsAdapter={router} />

Operation modes — mode="apply"

The URL demo above runs in the default immediate mode (live, debounced). Set mode="apply" to hold the committed state until the Apply button: bind:values (and any qsKey write) update only on Apply, while onchange keeps firing live — handy for an "N results" preview before committing.

Live onchange has fired 0 time(s) — but the committed bind:values below only changes when you press Apply.

{}
<!-- immediate (default): live as you drag, throttled with debounce -->
<RangeGroup {rows} bind:values debounce={200} />

<!-- apply: values / qsKey write commit only when Apply is pressed -->
<RangeGroup {rows} mode="apply" bind:values />

Svelte usage

The <RangeGroup> wrapper renders the whole structure from a rows array. For the common case, bind:values gives you the live payload (keyed by key) with no event wiring; onchange / onapply / onreset remain for side effects, and qsKey (above) for URL sync.

<script lang="ts">
  import { RangeGroup, type RangeGroupValues } from '@keenmate/svelte-pure-admin';
  let values = $state<RangeGroupValues>({});
</script>

<!-- Two-way bound values — no event wiring needed -->
<RangeGroup
  rows={[
    { key: 'age', label: 'Age', min: 18, max: 80, valueMin: 25, valueMax: 60 },
    { key: 'salary', label: 'Salary', min: 0, max: 200000, step: 5000,
      valueMin: 40000, valueMax: 200000, prefix: '$', thousands: true },
    { key: 'children', label: 'Children', min: 0, max: 8,
      mode: 'single', bound: 'gte', value: 2 }
  ]}
  bind:values
/>

<!-- …or hook side effects instead (localStorage, fetch, …) -->
<RangeGroup {rows} onapply={(v) => save(v)} />

Markup

One row per dimension. Positioning is driven in 0–100% via CSS custom properties on logical inset properties, so RTL mirrors automatically.

<div class="pa-range-group" data-range-group>
  <button class="pa-range-group__toggle" data-range-group-toggle aria-expanded="false">
    <!-- range-group.js fills this with "LABEL value / …" segments -->
    <span class="pa-range-group__summary" data-range-group-summary></span>
    <i class="fas fa-chevron-down pa-range-group__caret"></i>
  </button>

  <div class="pa-range-group__panel" data-range-group-panel>
    <div class="pa-range-group__row">
      <div class="pa-range-group__row-head">
        <span class="pa-range-group__row-label">Age</span>
        <span class="pa-range-group__row-value" data-range-output></span>
      </div>
      <div class="pa-range" data-range
           data-key="age" data-min="18" data-max="80"
           data-value-min="25" data-value-max="60">
        <div class="pa-range__rail">
          <div class="pa-range__track"></div>
          <div class="pa-range__fill" data-range-fill></div>
          <button class="pa-range__thumb pa-range__thumb--min" data-range-thumb="min"></button>
          <button class="pa-range__thumb pa-range__thumb--max" data-range-thumb="max"></button>
        </div>
      </div>
    </div>
    <!-- …more rows… -->
    <div class="pa-range-group__actions">
      <button class="pa-btn pa-btn--sm pa-btn--ghost" data-range-group-reset>Reset</button>
      <button class="pa-btn pa-btn--sm pa-btn--primary" data-range-group-apply>Apply</button>
    </div>
  </div>
</div>

CSS Classes Reference

Compact control

  • pa-range-group - Root wrapper (toggle + floating panel)
  • pa-range-group__toggle - The button summarising the filters
  • pa-range-group__summary - Single-line "LABEL value / …" readout host
  • pa-range-group__seg-label - A dimension's label in the summary
  • pa-range-group__seg-value - A dimension's value in the summary
  • pa-range-group__seg-value--empty - Muted "Any" value
  • pa-range-group__seg-sep - The " / " separator
  • pa-range-group__caret - Dropdown chevron (rotates when open)
  • pa-range-group--open - State: panel open (on the root)

Floating panel

  • pa-range-group__panel - The floating panel (reparented to body when open)
  • pa-range-group__panel--open - State: shown
  • pa-range-group__row - One dimension (head + slider)
  • pa-range-group__row-head - Row label + value line
  • pa-range-group__row-label - Row label
  • pa-range-group__row-value - Row value readout
  • pa-range-group__row-value--empty - Muted "Any" readout
  • pa-range-group__actions - Reset / Apply footer

Slider primitive

  • pa-range - Slider (dual-thumb by default)
  • pa-range__rail - Inner rail the thumbs travel along
  • pa-range__track - Full track
  • pa-range__fill - Selected-range fill
  • pa-range__thumb - A handle
  • pa-range__thumb--min / --max - Low / high handle
  • pa-range--single - Single-thumb (threshold) mode
  • pa-range--disabled - Non-interactive state

Handle shapes

  • pa-range--handle-rect - Rounded rectangle handles
  • pa-range--handle-bar - Thin vertical bar handles
  • pa-range--handle-arrow - Chevron handles
  • pa-range--handle-needle - Downward-triangle "needle" handles

Tick marks

  • pa-range__ticks - Tick container (built by JS from data-ticks)
  • pa-range__tick - A minor tick mark
  • pa-range__tick--major - A major tick mark (longer)
  • pa-range__tick-labels - Container for the value labels
  • pa-range__tick-label - A single major-tick label
  • pa-range--ticks-labeled - Row modifier reserving the label band

Theming tokens (CSS variables)

  • --pa-range-track / -fill - Track / fill colour
  • --pa-range-thumb-bg / -thumb-border / -thumb-border-hover - Handle colours
  • --pa-range-focus-ring - Thumb focus / active ring
  • --pa-range-tick / -tick-major - Minor / major tick colour
  • --pa-range-track-height / -thumb-size - Structural sizes
  • --pa-range-group-panel-min-width - Floating panel width

Keyboard Shortcuts

No shortcuts registered.