@astralarium/repo-ui 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +61 -2
- package/dist/error-boundary.d.ts +29 -0
- package/dist/error-boundary.js +30 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +23 -0
- package/dist/l10n/repo-ui/en.json +14 -0
- package/dist/lazy-google-analytics.d.ts +4 -0
- package/dist/lazy-google-analytics.js +34 -0
- package/dist/page-tree.d.ts +25 -0
- package/dist/page-tree.js +45 -0
- package/dist/responsive-table.d.ts +54 -0
- package/dist/responsive-table.js +180 -0
- package/dist/router/dev-route.d.ts +29 -0
- package/dist/router/dev-route.js +15 -0
- package/dist/router/index.d.ts +5 -0
- package/dist/router/index.js +5 -0
- package/dist/router/tab-bar.d.ts +12 -0
- package/dist/router/tab-bar.js +98 -0
- package/dist/router/tab-bar.messages.d.json.ts +7 -0
- package/dist/router/tab-bar.messages.json +6 -0
- package/dist/router/tabs.d.ts +21 -0
- package/dist/router/tabs.js +37 -0
- package/dist/router/use-search-param.d.ts +5 -0
- package/dist/router/use-search-param.js +46 -0
- package/dist/slider/index.d.ts +8 -0
- package/dist/slider/index.js +7 -0
- package/dist/slider/scrubber-state.d.ts +118 -0
- package/dist/slider/scrubber-state.js +179 -0
- package/dist/slider/scrubber.d.ts +77 -0
- package/dist/slider/scrubber.js +664 -0
- package/dist/slider/slider-parts.d.ts +16 -0
- package/dist/slider/slider-parts.js +11 -0
- package/dist/slider/slider-state.d.ts +75 -0
- package/dist/slider/slider-state.js +183 -0
- package/dist/slider/slider.d.ts +100 -0
- package/dist/slider/slider.js +966 -0
- package/dist/slider/slider.messages.d.json.ts +8 -0
- package/dist/slider/slider.messages.json +10 -0
- package/dist/slider/timeline-scrubber-state.d.ts +13 -0
- package/dist/slider/timeline-scrubber-state.js +21 -0
- package/dist/slider/timeline-scrubber.d.ts +26 -0
- package/dist/slider/timeline-scrubber.js +142 -0
- package/dist/sorted.d.ts +16 -0
- package/dist/sorted.js +40 -0
- package/dist/status.d.ts +9 -0
- package/dist/status.js +55 -0
- package/dist/sticky-height.d.ts +10 -0
- package/dist/sticky-height.js +133 -0
- package/dist/storage.d.ts +21 -0
- package/dist/storage.js +42 -0
- package/dist/testkit/index.d.ts +12 -0
- package/dist/testkit/index.js +42 -0
- package/dist/theme/index.d.ts +3 -0
- package/dist/theme/index.js +3 -0
- package/dist/theme/theme-context.d.ts +11 -0
- package/dist/theme/theme-context.js +68 -0
- package/dist/theme/theme.d.ts +28 -0
- package/dist/theme/theme.js +37 -0
- package/dist/theme/themed-component.d.ts +8 -0
- package/dist/theme/themed-component.js +10 -0
- package/dist/timeline.d.ts +34 -0
- package/dist/timeline.js +47 -0
- package/dist/tree/index.d.ts +2 -0
- package/dist/tree/index.js +1 -0
- package/dist/tree/tree.d.ts +27 -0
- package/dist/tree/tree.js +299 -0
- package/dist/tree/use-tree.d.ts +38 -0
- package/dist/tree/use-tree.js +155 -0
- package/dist/use-copy-feedback.d.ts +15 -0
- package/dist/use-copy-feedback.js +72 -0
- package/dist/use-debounce.d.ts +3 -0
- package/dist/use-debounce.js +66 -0
- package/dist/use-external-script.d.ts +10 -0
- package/dist/use-external-script.js +73 -0
- package/dist/use-frame.d.ts +3 -0
- package/dist/use-frame.js +37 -0
- package/dist/use-hover-focus.d.ts +18 -0
- package/dist/use-hover-focus.js +87 -0
- package/dist/use-media-query.d.ts +5 -0
- package/dist/use-media-query.js +42 -0
- package/dist/use-pausable-hover.d.ts +27 -0
- package/dist/use-pausable-hover.js +100 -0
- package/dist/use-pulse-style.d.ts +3 -0
- package/dist/use-pulse-style.js +19 -0
- package/dist/use-sticky-height.d.ts +10 -0
- package/dist/use-sticky-height.js +37 -0
- package/l10n.json +12 -0
- package/package.json +84 -3
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A `<nav>` of tabs. Each tab carries `data-slot="tab"`; the current tab
|
|
5
|
+
* carries `data-status="active"` and `aria-current`: `"page"` on a link,
|
|
6
|
+
* `"true"` on a button. Everything visual comes from the app.
|
|
7
|
+
*/
|
|
8
|
+
import { c as _c } from "react/compiler-runtime";
|
|
9
|
+
import { useFormat } from "@astralarium/localizable";
|
|
10
|
+
import { Link } from "@tanstack/react-router";
|
|
11
|
+
import messages from "./tab-bar.messages.json" with { type: "json" };
|
|
12
|
+
/** A link tab's default `activeOptions`: current on its exact path,
|
|
13
|
+
* search aside. */
|
|
14
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
15
|
+
const TAB_ACTIVE_OPTIONS = {
|
|
16
|
+
exact: true,
|
|
17
|
+
includeSearch: false
|
|
18
|
+
};
|
|
19
|
+
/** A tab bar: router `Link`s current on their exact path, search aside,
|
|
20
|
+
* unless the link sets `activeOptions`; buttons current while `active`.
|
|
21
|
+
* Named "Tabs" unless `aria-label` says otherwise. */
|
|
22
|
+
export function TabBar(t0) {
|
|
23
|
+
const $ = _c(15);
|
|
24
|
+
let props;
|
|
25
|
+
let tabClassName;
|
|
26
|
+
let tabs;
|
|
27
|
+
if ($[0] !== t0) {
|
|
28
|
+
({
|
|
29
|
+
tabs,
|
|
30
|
+
tabClassName,
|
|
31
|
+
...props
|
|
32
|
+
} = t0);
|
|
33
|
+
$[0] = t0;
|
|
34
|
+
$[1] = props;
|
|
35
|
+
$[2] = tabClassName;
|
|
36
|
+
$[3] = tabs;
|
|
37
|
+
} else {
|
|
38
|
+
props = $[1];
|
|
39
|
+
tabClassName = $[2];
|
|
40
|
+
tabs = $[3];
|
|
41
|
+
}
|
|
42
|
+
const format = useFormat();
|
|
43
|
+
let t1;
|
|
44
|
+
if ($[4] !== format) {
|
|
45
|
+
t1 = format(messages.barName);
|
|
46
|
+
$[4] = format;
|
|
47
|
+
$[5] = t1;
|
|
48
|
+
} else {
|
|
49
|
+
t1 = $[5];
|
|
50
|
+
}
|
|
51
|
+
let t2;
|
|
52
|
+
if ($[6] !== tabClassName || $[7] !== tabs) {
|
|
53
|
+
let t3;
|
|
54
|
+
if ($[9] !== tabClassName) {
|
|
55
|
+
t3 = tab => tab.link ? /*#__PURE__*/_jsx(Link, {
|
|
56
|
+
...tab.link,
|
|
57
|
+
activeOptions: tab.link.activeOptions ?? TAB_ACTIVE_OPTIONS,
|
|
58
|
+
className: tabClassName,
|
|
59
|
+
"data-slot": "tab",
|
|
60
|
+
children: tab.label
|
|
61
|
+
}, tab.name) : /*#__PURE__*/_jsx("button", {
|
|
62
|
+
type: "button",
|
|
63
|
+
onClick: () => tab.onClick?.(tab),
|
|
64
|
+
className: tabClassName,
|
|
65
|
+
"aria-current": tab.active ? "true" : undefined,
|
|
66
|
+
"data-slot": "tab",
|
|
67
|
+
"data-status": tab.active ? "active" : undefined,
|
|
68
|
+
children: tab.label
|
|
69
|
+
}, tab.name);
|
|
70
|
+
$[9] = tabClassName;
|
|
71
|
+
$[10] = t3;
|
|
72
|
+
} else {
|
|
73
|
+
t3 = $[10];
|
|
74
|
+
}
|
|
75
|
+
t2 = tabs.map(t3);
|
|
76
|
+
$[6] = tabClassName;
|
|
77
|
+
$[7] = tabs;
|
|
78
|
+
$[8] = t2;
|
|
79
|
+
} else {
|
|
80
|
+
t2 = $[8];
|
|
81
|
+
}
|
|
82
|
+
let t3;
|
|
83
|
+
if ($[11] !== props || $[12] !== t1 || $[13] !== t2) {
|
|
84
|
+
t3 = /*#__PURE__*/_jsx("nav", {
|
|
85
|
+
"aria-label": t1,
|
|
86
|
+
"data-slot": "tab-bar",
|
|
87
|
+
...props,
|
|
88
|
+
children: t2
|
|
89
|
+
});
|
|
90
|
+
$[11] = props;
|
|
91
|
+
$[12] = t1;
|
|
92
|
+
$[13] = t2;
|
|
93
|
+
$[14] = t3;
|
|
94
|
+
} else {
|
|
95
|
+
t3 = $[14];
|
|
96
|
+
}
|
|
97
|
+
return t3;
|
|
98
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type LinkOptions } from "@tanstack/react-router";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
/** A tab: a router link, or a button. */
|
|
4
|
+
export interface Tab {
|
|
5
|
+
/** Identifies the tab; `buildTabs` selects by it. */
|
|
6
|
+
name: string;
|
|
7
|
+
label: ReactNode;
|
|
8
|
+
/** Destination of a link tab; current on its exact path, search
|
|
9
|
+
* aside, unless `activeOptions` says otherwise. */
|
|
10
|
+
link?: LinkOptions;
|
|
11
|
+
/** Press of a button tab. */
|
|
12
|
+
onClick?: (tab: Tab) => unknown;
|
|
13
|
+
/** The selected tab: current among button tabs. */
|
|
14
|
+
active?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Binds fixed tabs to a selection: `tabsOf(tab, setTab, onChange)` marks
|
|
17
|
+
* tab `tab` active and selects a pressed tab. */
|
|
18
|
+
export declare function buildTabs(tabs: readonly Omit<Tab, "onClick" | "active">[]): (tab: string, setTab: (name: string) => unknown, onChange?: () => unknown) => Tab[];
|
|
19
|
+
/** Selects a tab outside a `TabBar`, e.g. from a dropdown: navigates to a
|
|
20
|
+
* link tab, presses a button tab. */
|
|
21
|
+
export declare function useSelectTab(): (tab: Tab) => void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
3
|
+
/** Binds fixed tabs to a selection: `tabsOf(tab, setTab, onChange)` marks
|
|
4
|
+
* tab `tab` active and selects a pressed tab. */
|
|
5
|
+
export function buildTabs(tabs) {
|
|
6
|
+
return function tabsOf(tab, setTab, onChange) {
|
|
7
|
+
return tabs.map(x => ({
|
|
8
|
+
...x,
|
|
9
|
+
active: x.name === tab,
|
|
10
|
+
onClick: () => {
|
|
11
|
+
setTab(x.name);
|
|
12
|
+
onChange?.();
|
|
13
|
+
}
|
|
14
|
+
}));
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Selects a tab outside a `TabBar`, e.g. from a dropdown: navigates to a
|
|
18
|
+
* link tab, presses a button tab. */
|
|
19
|
+
export function useSelectTab() {
|
|
20
|
+
const $ = _c(2);
|
|
21
|
+
const navigate = useNavigate();
|
|
22
|
+
let t0;
|
|
23
|
+
if ($[0] !== navigate) {
|
|
24
|
+
t0 = tab => {
|
|
25
|
+
if (tab.link) {
|
|
26
|
+
navigate(tab.link);
|
|
27
|
+
} else {
|
|
28
|
+
tab.onClick?.(tab);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
$[0] = navigate;
|
|
32
|
+
$[1] = t0;
|
|
33
|
+
} else {
|
|
34
|
+
t0 = $[1];
|
|
35
|
+
}
|
|
36
|
+
return t0;
|
|
37
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** String-valued search param `name`, written back after `ms` idle
|
|
2
|
+
* (`useDebounce`), replacing the history entry and keeping the hash;
|
|
3
|
+
* empty text removes it. Other values read as JSON and write back as
|
|
4
|
+
* strings. */
|
|
5
|
+
export declare function useSearchParam(name: string, ms?: number): [string, (text: string) => void];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
import { useNavigate, useSearch } from "@tanstack/react-router";
|
|
3
|
+
import { useDebounce } from "../use-debounce.js";
|
|
4
|
+
/** A search param's value as text; empty when absent. */
|
|
5
|
+
function searchText(value) {
|
|
6
|
+
if (value === undefined || value === null) return "";
|
|
7
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
8
|
+
}
|
|
9
|
+
/** String-valued search param `name`, written back after `ms` idle
|
|
10
|
+
* (`useDebounce`), replacing the history entry and keeping the hash;
|
|
11
|
+
* empty text removes it. Other values read as JSON and write back as
|
|
12
|
+
* strings. */
|
|
13
|
+
export function useSearchParam(name, ms) {
|
|
14
|
+
const $ = _c(5);
|
|
15
|
+
let t0;
|
|
16
|
+
if ($[0] !== name) {
|
|
17
|
+
t0 = {
|
|
18
|
+
strict: false,
|
|
19
|
+
select: search => searchText(search[name])
|
|
20
|
+
};
|
|
21
|
+
$[0] = name;
|
|
22
|
+
$[1] = t0;
|
|
23
|
+
} else {
|
|
24
|
+
t0 = $[1];
|
|
25
|
+
}
|
|
26
|
+
const value = useSearch(t0);
|
|
27
|
+
const navigate = useNavigate();
|
|
28
|
+
let t1;
|
|
29
|
+
if ($[2] !== name || $[3] !== navigate) {
|
|
30
|
+
t1 = text => navigate({
|
|
31
|
+
to: ".",
|
|
32
|
+
search: prev => ({
|
|
33
|
+
...prev,
|
|
34
|
+
[name]: text || undefined
|
|
35
|
+
}),
|
|
36
|
+
hash: true,
|
|
37
|
+
replace: true
|
|
38
|
+
});
|
|
39
|
+
$[2] = name;
|
|
40
|
+
$[3] = navigate;
|
|
41
|
+
$[4] = t1;
|
|
42
|
+
} else {
|
|
43
|
+
t1 = $[4];
|
|
44
|
+
}
|
|
45
|
+
return useDebounce(value, t1, ms);
|
|
46
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Sliders and scrubbers over Base UI: parts, state and timeline scrubbing. */
|
|
2
|
+
export * from "./scrubber.tsx";
|
|
3
|
+
export type { ScrubberStep, ScrubberTick } from "./scrubber-state.ts";
|
|
4
|
+
export * from "./slider.tsx";
|
|
5
|
+
export * from "./slider-parts.ts";
|
|
6
|
+
export { type SliderContextValue, type SliderFill, type SliderSpan, type SliderTickLight, useSlider, useSliderTick, } from "./slider-state.ts";
|
|
7
|
+
export * from "./timeline-scrubber.tsx";
|
|
8
|
+
export { type TimelineMark, timelineNames } from "./timeline-scrubber-state.ts";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Sliders and scrubbers over Base UI: parts, state and timeline scrubbing. */
|
|
2
|
+
export * from "./scrubber.js";
|
|
3
|
+
export * from "./slider.js";
|
|
4
|
+
export * from "./slider-parts.js";
|
|
5
|
+
export { useSlider, useSliderTick } from "./slider-state.js";
|
|
6
|
+
export * from "./timeline-scrubber.js";
|
|
7
|
+
export { timelineNames } from "./timeline-scrubber-state.js";
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/** Scrubber rules: tick groups, landing, stepping, lighting, reach,
|
|
2
|
+
* and the split fill. */
|
|
3
|
+
import type { Slider as SliderPrimitive } from "@base-ui/react/slider";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
import type { SliderSpan } from "./slider-state.ts";
|
|
6
|
+
/** A position along a scrubber's track: a snap and step target. Named
|
|
7
|
+
* or glyphed, a mark, always drawn; bare, a stop, drawn faintly near
|
|
8
|
+
* the pointer. */
|
|
9
|
+
export interface ScrubberTick {
|
|
10
|
+
value: number;
|
|
11
|
+
/** Tooltip and spoken text; names sharing a value list together. */
|
|
12
|
+
name?: string;
|
|
13
|
+
/** Drawn at the tick; the lowest-ranked glyph at a value draws. Its
|
|
14
|
+
* mark carries the thumb's reach along the fill as `data-reach`:
|
|
15
|
+
* `ahead` short of the mark, `held` on it or within the hold window
|
|
16
|
+
* past it, `past` beyond. */
|
|
17
|
+
glyph?: ReactNode;
|
|
18
|
+
/** Lower draws over higher at a shared value; ties keep order. */
|
|
19
|
+
rank?: number;
|
|
20
|
+
}
|
|
21
|
+
/** The ticks at one value. */
|
|
22
|
+
export interface ScrubberTickGroup {
|
|
23
|
+
value: number;
|
|
24
|
+
/** The named or glyphed ticks, most prominent first; none for a
|
|
25
|
+
* stop. */
|
|
26
|
+
marks: readonly ScrubberTick[];
|
|
27
|
+
/** The marks' names, most prominent first. */
|
|
28
|
+
names: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
/** `ticks` grouped by value, ascending. */
|
|
31
|
+
export declare function groupTicks(ticks: readonly ScrubberTick[]): ScrubberTickGroup[];
|
|
32
|
+
/** The names of the `groups` at `value`, most prominent first. */
|
|
33
|
+
export declare function tickNames(groups: readonly ScrubberTickGroup[], value: number): readonly string[];
|
|
34
|
+
/** The names of the latest of `groups` at or before `value`, most
|
|
35
|
+
* prominent first. */
|
|
36
|
+
export declare function latestNames(groups: readonly ScrubberTickGroup[], value: number): readonly string[];
|
|
37
|
+
/** A key step: `by` toward `max` (1) or `min` (-1), resolved for
|
|
38
|
+
* orientation and direction; `large` with Shift or PageUp/PageDown. */
|
|
39
|
+
export interface ScrubberStep {
|
|
40
|
+
by: -1 | 1;
|
|
41
|
+
large: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Where a step from `from` lands: the adjacent tick; a large step's
|
|
44
|
+
* the adjacent mark, or tick where none is a mark. Undefined past the
|
|
45
|
+
* outermost. */
|
|
46
|
+
export declare function stepTick(groups: readonly ScrubberTickGroup[], from: number, { by, large }: ScrubberStep): number | undefined;
|
|
47
|
+
/** The ticks and range landings snap within. */
|
|
48
|
+
export interface ScrubberGrid {
|
|
49
|
+
groups: readonly ScrubberTickGroup[];
|
|
50
|
+
min: number;
|
|
51
|
+
max: number;
|
|
52
|
+
/** How near a tick a landing snaps to it. */
|
|
53
|
+
snapWindow: number;
|
|
54
|
+
}
|
|
55
|
+
/** Where `at` lands, within the range: snapping, the tick nearest it
|
|
56
|
+
* within the snap window. */
|
|
57
|
+
export declare function landAt({ groups, min, max, snapWindow }: ScrubberGrid, at: number, snaps: boolean): number;
|
|
58
|
+
/** A value change, as the scrubber reads it. */
|
|
59
|
+
export interface ScrubberChange {
|
|
60
|
+
/** Base UI's value; for a thumb key while playing, the end it heads
|
|
61
|
+
* for. */
|
|
62
|
+
next: number;
|
|
63
|
+
reason: SliderPrimitive.Root.ChangeEventReason;
|
|
64
|
+
/** A step's way: `next`'s side of Base UI's value, or a thumb key's
|
|
65
|
+
* (`keyWay`). */
|
|
66
|
+
by: -1 | 1;
|
|
67
|
+
/** The key pressed, on a keyboard change. */
|
|
68
|
+
key?: string;
|
|
69
|
+
/** Shift held, where the event carries modifiers. */
|
|
70
|
+
shiftKey?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/** What a change does: lands at `land`, or steps by `step`, landing at
|
|
73
|
+
* `land` unless a step handler takes the step. */
|
|
74
|
+
export type ScrubberMove = {
|
|
75
|
+
land: number;
|
|
76
|
+
} | {
|
|
77
|
+
step: ScrubberStep;
|
|
78
|
+
land: number | undefined;
|
|
79
|
+
};
|
|
80
|
+
/** Which way a thumb key heads, resolved for orientation and direction:
|
|
81
|
+
* Home and End toward their ends; null for other keys. */
|
|
82
|
+
export declare function keyWay(key: string, rtl: boolean): -1 | 1 | null;
|
|
83
|
+
/** What `change` does to the scrubber at `current`: presses and drags
|
|
84
|
+
* land snapped (`snapping`, unless the event says whether Shift is
|
|
85
|
+
* held), arrows, Page keys, and input changes step their way, Home and
|
|
86
|
+
* End land on the ends. */
|
|
87
|
+
export declare function resolveChange(grid: ScrubberGrid, { next, reason, by, key, shiftKey }: ScrubberChange, { current, snapping }: {
|
|
88
|
+
current: number;
|
|
89
|
+
snapping: boolean;
|
|
90
|
+
}): ScrubberMove;
|
|
91
|
+
/** The tick an off-tick `target` shows: the latest at or before it;
|
|
92
|
+
* null on a tick, before the first, or with no target. */
|
|
93
|
+
export declare function hintedTick(groups: readonly ScrubberTickGroup[], target: number | null): number | null;
|
|
94
|
+
/** A stop drawn near the pointer. */
|
|
95
|
+
export interface NearStop {
|
|
96
|
+
value: number;
|
|
97
|
+
/** 1 for the nearest, falling with each stop farther. */
|
|
98
|
+
fade: number;
|
|
99
|
+
}
|
|
100
|
+
/** The `count` of `stops` nearest `pointer`, fading with distance. */
|
|
101
|
+
export declare function nearStops(stops: readonly number[], pointer: number, count: number): NearStop[];
|
|
102
|
+
/** How far `at` has come along `marks`: one per mark it stands on, two
|
|
103
|
+
* per mark behind it. Changes exactly as `at` reaches or leaves a
|
|
104
|
+
* mark. */
|
|
105
|
+
export declare function crossings(marks: readonly number[], at: number): number;
|
|
106
|
+
/** Where the thumb stands by a mark (`data-reach`). */
|
|
107
|
+
export type ScrubberReach = "ahead" | "held" | "past";
|
|
108
|
+
/** The thumb at `value` by the mark at `at`, along the way `fill`
|
|
109
|
+
* grows: `held` on it or up to `hold` past it. */
|
|
110
|
+
export declare function tickReach(value: number, at: number, hold: number, fill: "start" | "end"): ScrubberReach;
|
|
111
|
+
/** A range segment: a run of the fill and how a press changes it. */
|
|
112
|
+
export interface RangeSegment {
|
|
113
|
+
change: "kept" | "shrink" | "grow";
|
|
114
|
+
span: SliderSpan;
|
|
115
|
+
}
|
|
116
|
+
/** The fill over `shown`, split by what a press leaving `predicted`
|
|
117
|
+
* keeps, shrinks, and grows. */
|
|
118
|
+
export declare function rangeSegments(shown: SliderSpan, predicted: SliderSpan | null): RangeSegment[];
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { adjacent, nearest } from "../sorted.js";
|
|
2
|
+
/** Whether `tick` draws as a mark. */
|
|
3
|
+
function isMark(tick) {
|
|
4
|
+
return tick.name !== undefined || tick.glyph !== undefined;
|
|
5
|
+
}
|
|
6
|
+
/** `ticks` grouped by value, ascending. */
|
|
7
|
+
export function groupTicks(ticks) {
|
|
8
|
+
const at = new Map();
|
|
9
|
+
for (const tick of ticks) {
|
|
10
|
+
at.set(tick.value, [...(at.get(tick.value) ?? []), tick]);
|
|
11
|
+
}
|
|
12
|
+
return [...at].sort(([a], [b]) => a - b).map(([value, here]) => {
|
|
13
|
+
const marks = here.filter(isMark).sort((a, b) => (a.rank ?? 0) - (b.rank ?? 0));
|
|
14
|
+
return {
|
|
15
|
+
value,
|
|
16
|
+
marks,
|
|
17
|
+
names: marks.flatMap(mark => mark.name === undefined ? [] : [mark.name])
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** The names of the `groups` at `value`, most prominent first. */
|
|
22
|
+
export function tickNames(groups, value) {
|
|
23
|
+
return groups.find(group => group.value === value)?.names ?? [];
|
|
24
|
+
}
|
|
25
|
+
/** The latest of `groups` at or before `value`; undefined before the
|
|
26
|
+
* first. */
|
|
27
|
+
function latestTick(groups, value) {
|
|
28
|
+
return groups.findLast(group => group.value <= value);
|
|
29
|
+
}
|
|
30
|
+
/** The names of the latest of `groups` at or before `value`, most
|
|
31
|
+
* prominent first. */
|
|
32
|
+
export function latestNames(groups, value) {
|
|
33
|
+
return latestTick(groups, value)?.names ?? [];
|
|
34
|
+
}
|
|
35
|
+
/** Where a step from `from` lands: the adjacent tick; a large step's
|
|
36
|
+
* the adjacent mark, or tick where none is a mark. Undefined past the
|
|
37
|
+
* outermost. */
|
|
38
|
+
export function stepTick(groups, from, {
|
|
39
|
+
by,
|
|
40
|
+
large
|
|
41
|
+
}) {
|
|
42
|
+
const marks = groups.filter(group => group.marks.length > 0);
|
|
43
|
+
const over = (large && marks.length > 0 ? marks : groups).map(group => group.value);
|
|
44
|
+
return adjacent(over, from, by);
|
|
45
|
+
}
|
|
46
|
+
/** Where `at` lands, within the range: snapping, the tick nearest it
|
|
47
|
+
* within the snap window. */
|
|
48
|
+
export function landAt({
|
|
49
|
+
groups,
|
|
50
|
+
min,
|
|
51
|
+
max,
|
|
52
|
+
snapWindow
|
|
53
|
+
}, at, snaps) {
|
|
54
|
+
const ticks = groups.map(group => group.value);
|
|
55
|
+
return Math.min(max, Math.max(min, snaps ? nearest(ticks, at, snapWindow) : at));
|
|
56
|
+
}
|
|
57
|
+
/** A step from `current` by `step`: to the adjacent tick; without
|
|
58
|
+
* ticks, to `next`. */
|
|
59
|
+
function stepMove({
|
|
60
|
+
groups
|
|
61
|
+
}, current, next, step) {
|
|
62
|
+
return {
|
|
63
|
+
step,
|
|
64
|
+
land: groups.length === 0 ? next : stepTick(groups, current, step)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Which way a thumb key heads, resolved for orientation and direction:
|
|
68
|
+
* Home and End toward their ends; null for other keys. */
|
|
69
|
+
export function keyWay(key, rtl) {
|
|
70
|
+
switch (key) {
|
|
71
|
+
case "ArrowUp":
|
|
72
|
+
case "PageUp":
|
|
73
|
+
case "End":
|
|
74
|
+
return 1;
|
|
75
|
+
case "ArrowDown":
|
|
76
|
+
case "PageDown":
|
|
77
|
+
case "Home":
|
|
78
|
+
return -1;
|
|
79
|
+
case "ArrowRight":
|
|
80
|
+
return rtl ? -1 : 1;
|
|
81
|
+
case "ArrowLeft":
|
|
82
|
+
return rtl ? 1 : -1;
|
|
83
|
+
default:
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** What `change` does to the scrubber at `current`: presses and drags
|
|
88
|
+
* land snapped (`snapping`, unless the event says whether Shift is
|
|
89
|
+
* held), arrows, Page keys, and input changes step their way, Home and
|
|
90
|
+
* End land on the ends. */
|
|
91
|
+
export function resolveChange(grid, {
|
|
92
|
+
next,
|
|
93
|
+
reason,
|
|
94
|
+
by,
|
|
95
|
+
key,
|
|
96
|
+
shiftKey
|
|
97
|
+
}, {
|
|
98
|
+
current,
|
|
99
|
+
snapping
|
|
100
|
+
}) {
|
|
101
|
+
switch (reason) {
|
|
102
|
+
case "track-press":
|
|
103
|
+
case "drag":
|
|
104
|
+
return {
|
|
105
|
+
land: landAt(grid, next, shiftKey === undefined ? snapping : !shiftKey)
|
|
106
|
+
};
|
|
107
|
+
// Assistive tech adjusts the thumb a step at a time.
|
|
108
|
+
case "input-change":
|
|
109
|
+
return stepMove(grid, current, next, {
|
|
110
|
+
by,
|
|
111
|
+
large: false
|
|
112
|
+
});
|
|
113
|
+
case "keyboard":
|
|
114
|
+
if (key === "Home" || key === "End") return {
|
|
115
|
+
land: next
|
|
116
|
+
};
|
|
117
|
+
return stepMove(grid, current, next, {
|
|
118
|
+
by,
|
|
119
|
+
large: shiftKey === true || key === "PageUp" || key === "PageDown"
|
|
120
|
+
});
|
|
121
|
+
case "none":
|
|
122
|
+
return {
|
|
123
|
+
land: next
|
|
124
|
+
};
|
|
125
|
+
default:
|
|
126
|
+
return reason;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** The tick an off-tick `target` shows: the latest at or before it;
|
|
130
|
+
* null on a tick, before the first, or with no target. */
|
|
131
|
+
export function hintedTick(groups, target) {
|
|
132
|
+
if (target === null) return null;
|
|
133
|
+
const tick = latestTick(groups, target);
|
|
134
|
+
return tick === undefined || tick.value === target ? null : tick.value;
|
|
135
|
+
}
|
|
136
|
+
/** The `count` of `stops` nearest `pointer`, fading with distance. */
|
|
137
|
+
export function nearStops(stops, pointer, count) {
|
|
138
|
+
return [...stops].sort((a, b) => Math.abs(a - pointer) - Math.abs(b - pointer)).slice(0, count).map((value, rank) => ({
|
|
139
|
+
value,
|
|
140
|
+
fade: 1 - rank / count
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
/** How far `at` has come along `marks`: one per mark it stands on, two
|
|
144
|
+
* per mark behind it. Changes exactly as `at` reaches or leaves a
|
|
145
|
+
* mark. */
|
|
146
|
+
export function crossings(marks, at) {
|
|
147
|
+
let count = 0;
|
|
148
|
+
for (const mark of marks) count += mark < at ? 2 : mark === at ? 1 : 0;
|
|
149
|
+
return count;
|
|
150
|
+
}
|
|
151
|
+
/** The thumb at `value` by the mark at `at`, along the way `fill`
|
|
152
|
+
* grows: `held` on it or up to `hold` past it. */
|
|
153
|
+
export function tickReach(value, at, hold, fill) {
|
|
154
|
+
const past = fill === "start" ? value - at : at - value;
|
|
155
|
+
return past < 0 ? "ahead" : past <= hold ? "held" : "past";
|
|
156
|
+
}
|
|
157
|
+
/** `a`'s runs outside `b`. */
|
|
158
|
+
function spanMinus([a0, a1], [b0, b1]) {
|
|
159
|
+
return [[a0, Math.min(a1, b0)], [Math.max(a0, b1), a1]].filter(([lo, hi]) => hi > lo);
|
|
160
|
+
}
|
|
161
|
+
/** The fill over `shown`, split by what a press leaving `predicted`
|
|
162
|
+
* keeps, shrinks, and grows. */
|
|
163
|
+
export function rangeSegments(shown, predicted) {
|
|
164
|
+
if (predicted === null) return [{
|
|
165
|
+
change: "kept",
|
|
166
|
+
span: shown
|
|
167
|
+
}];
|
|
168
|
+
const kept = [Math.max(shown[0], predicted[0]), Math.min(shown[1], predicted[1])];
|
|
169
|
+
return [...(kept[1] > kept[0] ? [{
|
|
170
|
+
change: "kept",
|
|
171
|
+
span: kept
|
|
172
|
+
}] : []), ...spanMinus(shown, predicted).map(span => ({
|
|
173
|
+
change: "shrink",
|
|
174
|
+
span
|
|
175
|
+
})), ...spanMinus(predicted, shown).map(span => ({
|
|
176
|
+
change: "grow",
|
|
177
|
+
span
|
|
178
|
+
}))];
|
|
179
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Slider as SliderPrimitive } from "@base-ui/react/slider";
|
|
2
|
+
import { type ScrubberStep, type ScrubberTick } from "./scrubber-state.ts";
|
|
3
|
+
import type { SliderRootProps, SliderThumbProps } from "./slider.tsx";
|
|
4
|
+
import { type SliderParts } from "./slider-parts.ts";
|
|
5
|
+
/** What moved the value: Base UI's reasons, `track-press` as the
|
|
6
|
+
* scrubber lands a press, `keyboard` as it lands a thumb key while
|
|
7
|
+
* playing, or `modifier` as Shift toggles snapping mid-drag. */
|
|
8
|
+
export type ScrubberChangeDetails = Exclude<SliderPrimitive.Root.ChangeEventDetails, {
|
|
9
|
+
reason: "track-press";
|
|
10
|
+
}> | {
|
|
11
|
+
reason: "track-press";
|
|
12
|
+
event: PointerEvent;
|
|
13
|
+
} | {
|
|
14
|
+
reason: "keyboard";
|
|
15
|
+
event: KeyboardEvent;
|
|
16
|
+
} | {
|
|
17
|
+
reason: "modifier";
|
|
18
|
+
event: KeyboardEvent;
|
|
19
|
+
};
|
|
20
|
+
/** Where a thumb key steps from the playhead: between the ticks, or
|
|
21
|
+
* through the step handler. */
|
|
22
|
+
type ScrubberStepping = {
|
|
23
|
+
playhead?: null;
|
|
24
|
+
/** Landings snap to these; arrows step between them. */
|
|
25
|
+
ticks?: readonly ScrubberTick[];
|
|
26
|
+
/** Arrows, Page keys, and input changes (assistive tech adjusting
|
|
27
|
+
* the thumb) step through this in place of the ticks: it lands
|
|
28
|
+
* the step itself. Home and End land on the ends regardless. */
|
|
29
|
+
onStep?: (step: ScrubberStep) => void;
|
|
30
|
+
} | {
|
|
31
|
+
/** The shown value, read every frame while set (playback): the
|
|
32
|
+
* thumb and fill glide with it (`SliderRoot` `glide`); `value`
|
|
33
|
+
* stays the spoken one. Thumb keys, input changes, and presses
|
|
34
|
+
* move from it. */
|
|
35
|
+
playhead: (() => number) | null;
|
|
36
|
+
ticks: readonly ScrubberTick[];
|
|
37
|
+
onStep?: (step: ScrubberStep) => void;
|
|
38
|
+
} | {
|
|
39
|
+
playhead: (() => number) | null;
|
|
40
|
+
ticks?: readonly ScrubberTick[];
|
|
41
|
+
onStep: (step: ScrubberStep) => void;
|
|
42
|
+
};
|
|
43
|
+
/** `Scrubber` props: a single-thumb slider's, over ticks. */
|
|
44
|
+
export type ScrubberProps = Omit<SliderRootProps<number>, "value" | "defaultValue" | "onValueChange" | "onValueCommitted" | "children" | "fill" | "minStepsBetweenValues" | "thumbCollisionBehavior"> & {
|
|
45
|
+
value: number;
|
|
46
|
+
/** Lands at `value`: presses, drags, input changes, and steps. */
|
|
47
|
+
onValueChange: (value: number, details: ScrubberChangeDetails) => void;
|
|
48
|
+
/** How near a tick a landing snaps to it, as a share of the range; 0
|
|
49
|
+
* lands on ticks alone. A mark holds the thumb as far past it
|
|
50
|
+
* (`data-reach`); playing, the thumb passes marks. */
|
|
51
|
+
snapShare?: number;
|
|
52
|
+
/** A value shown ahead of a pick elsewhere: the thumb there,
|
|
53
|
+
* tentative (`data-tentative`), its tick lit. */
|
|
54
|
+
tentative?: number | null;
|
|
55
|
+
/** The pointer previews its press: the fill it keeps, shrinks, and
|
|
56
|
+
* grows, and the tick it lands on. */
|
|
57
|
+
pointerPreview?: boolean;
|
|
58
|
+
/** The run the range fills. */
|
|
59
|
+
fill?: "start" | "end";
|
|
60
|
+
/** The spoken value at `value`, given the names of the latest tick at
|
|
61
|
+
* or before it; by default the names of the ticks at `value` as a
|
|
62
|
+
* conjunction in the messages locale, else the formatted value. */
|
|
63
|
+
valueText?: (value: number, names: readonly string[]) => string;
|
|
64
|
+
/** The thumb's props (accessible name); its ref, style, and focus
|
|
65
|
+
* handlers merge with the scrubber's own. */
|
|
66
|
+
thumb?: Omit<SliderThumbProps, "index" | "getAriaValueText">;
|
|
67
|
+
/** The parts drawn; the unstyled ones by default. */
|
|
68
|
+
parts?: SliderParts;
|
|
69
|
+
} & ScrubberStepping;
|
|
70
|
+
/** A single-thumb slider over ticks: landings snap to them, arrows
|
|
71
|
+
* step between them, marks draw always, stops near the pointer. The
|
|
72
|
+
* pointer previews its press; Shift lands at the pointer. State shows
|
|
73
|
+
* as `data-tentative` on the root, `data-change` on the fill's
|
|
74
|
+
* segments (`kept`, `shrink`, `grow`), `data-light` and `data-reach`
|
|
75
|
+
* on ticks, and `data-hover` on the thumb. */
|
|
76
|
+
export declare function Scrubber({ value, onValueChange, ticks, snapShare, tentative, playhead, pointerPreview, fill, onStep, valueText, thumb, parts, onDraggingChange, min, max, disabled, onKeyDownCapture, ...props }: ScrubberProps): import("react/jsx-runtime").JSX.Element;
|
|
77
|
+
export {};
|