@seekalfred/primitives 0.6.0 → 0.7.1
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/README.md +19 -11
- package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
- package/dist/activity-heatmap/activity-heatmap.js +699 -0
- package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
- package/dist/activity-heatmap/index.d.ts +2 -0
- package/dist/activity-heatmap/index.js +2 -0
- package/dist/alert/alert.d.ts +5 -1
- package/dist/alert/alert.js +68 -7
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert-dialog/alert-dialog.d.ts +45 -18
- package/dist/alert-dialog/alert-dialog.js +128 -9
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/alert-dialog/index.d.ts +2 -2
- package/dist/alert-dialog/index.js +2 -2
- package/dist/animated-counter/animated-counter.d.ts +38 -0
- package/dist/animated-counter/animated-counter.js +230 -0
- package/dist/animated-counter/animated-counter.js.map +1 -0
- package/dist/animated-counter/index.d.ts +2 -0
- package/dist/animated-counter/index.js +2 -0
- package/dist/announcement-bar/announcement-bar.d.ts +67 -0
- package/dist/announcement-bar/announcement-bar.js +364 -0
- package/dist/announcement-bar/announcement-bar.js.map +1 -0
- package/dist/announcement-bar/index.d.ts +2 -0
- package/dist/announcement-bar/index.js +2 -0
- package/dist/badge/badge.d.ts +1 -1
- package/dist/bar-chart/bar-chart.d.ts +51 -0
- package/dist/bar-chart/bar-chart.js +565 -0
- package/dist/bar-chart/bar-chart.js.map +1 -0
- package/dist/bar-chart/index.d.ts +2 -0
- package/dist/bar-chart/index.js +2 -0
- package/dist/breadcrumb/breadcrumb.d.ts +2 -2
- package/dist/breadcrumb/breadcrumb.js +12 -3
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/brush-chart/brush-chart.d.ts +64 -0
- package/dist/brush-chart/brush-chart.js +1056 -0
- package/dist/brush-chart/brush-chart.js.map +1 -0
- package/dist/brush-chart/index.d.ts +2 -0
- package/dist/brush-chart/index.js +2 -0
- package/dist/button/button.d.ts +2 -2
- package/dist/button/button.js +31 -21
- package/dist/button/button.js.map +1 -1
- package/dist/button-group/button-group.js +3 -4
- package/dist/button-group/button-group.js.map +1 -1
- package/dist/card/card.js +1 -1
- package/dist/card/card.js.map +1 -1
- package/dist/card-stack/card-stack.d.ts +54 -0
- package/dist/card-stack/card-stack.js +492 -0
- package/dist/card-stack/card-stack.js.map +1 -0
- package/dist/card-stack/index.d.ts +2 -0
- package/dist/card-stack/index.js +2 -0
- package/dist/carousel/carousel.d.ts +56 -0
- package/dist/carousel/carousel.js +389 -0
- package/dist/carousel/carousel.js.map +1 -0
- package/dist/carousel/index.d.ts +2 -0
- package/dist/carousel/index.js +2 -0
- package/dist/checkbox/checkbox.js +4 -4
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/chip-group/chip-group.d.ts +37 -0
- package/dist/chip-group/chip-group.js +148 -0
- package/dist/chip-group/chip-group.js.map +1 -0
- package/dist/chip-group/index.d.ts +2 -0
- package/dist/chip-group/index.js +2 -0
- package/dist/countdown/countdown.d.ts +45 -0
- package/dist/countdown/countdown.js +298 -0
- package/dist/countdown/countdown.js.map +1 -0
- package/dist/countdown/index.d.ts +2 -0
- package/dist/countdown/index.js +2 -0
- package/dist/date-picker/picker-parts.js +1 -1
- package/dist/date-picker/picker-parts.js.map +1 -1
- package/dist/dialog/dialog.js +1 -1
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/donut-chart/donut-chart.d.ts +64 -0
- package/dist/donut-chart/donut-chart.js +576 -0
- package/dist/donut-chart/donut-chart.js.map +1 -0
- package/dist/donut-chart/index.d.ts +2 -0
- package/dist/donut-chart/index.js +2 -0
- package/dist/empty-state/empty-state.d.ts +21 -10
- package/dist/empty-state/empty-state.js +32 -26
- package/dist/empty-state/empty-state.js.map +1 -1
- package/dist/empty-state/index.d.ts +2 -2
- package/dist/empty-state/index.js +2 -2
- package/dist/file-drop-zone/file-drop-zone.js +1 -1
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
- package/dist/filter-bar/filter-bar.d.ts +47 -26
- package/dist/filter-bar/filter-bar.js +272 -156
- package/dist/filter-bar/filter-bar.js.map +1 -1
- package/dist/gauge/gauge.d.ts +45 -0
- package/dist/gauge/gauge.js +219 -0
- package/dist/gauge/gauge.js.map +1 -0
- package/dist/gauge/index.d.ts +2 -0
- package/dist/gauge/index.js +2 -0
- package/dist/in-view-title/in-view-title.d.ts +33 -0
- package/dist/in-view-title/in-view-title.js +119 -0
- package/dist/in-view-title/in-view-title.js.map +1 -0
- package/dist/in-view-title/index.d.ts +2 -0
- package/dist/in-view-title/index.js +2 -0
- package/dist/index.d.ts +49 -6
- package/dist/index.js +48 -8
- package/dist/lib/cn.js +11 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/field-message.js +28 -10
- package/dist/lib/field-message.js.map +1 -1
- package/dist/lib/field-surface.js +9 -8
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/flag-emoji.js +50 -0
- package/dist/lib/flag-emoji.js.map +1 -0
- package/dist/lib/flip.js +11 -3
- package/dist/lib/flip.js.map +1 -1
- package/dist/lib/menu-highlight.js +64 -0
- package/dist/lib/menu-highlight.js.map +1 -0
- package/dist/lib/motion.js +9 -2
- package/dist/lib/motion.js.map +1 -1
- package/dist/lib/rolling-value.js +1 -1
- package/dist/lib/rolling-value.js.map +1 -1
- package/dist/lib/spring-tween.js +500 -0
- package/dist/lib/spring-tween.js.map +1 -0
- package/dist/lib/swap-text.js +3 -1
- package/dist/lib/swap-text.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -0
- package/dist/line-chart/index.js +2 -0
- package/dist/line-chart/line-chart.d.ts +76 -0
- package/dist/line-chart/line-chart.js +611 -0
- package/dist/line-chart/line-chart.js.map +1 -0
- package/dist/mention-input/index.d.ts +2 -0
- package/dist/mention-input/index.js +2 -0
- package/dist/mention-input/mention-input.d.ts +98 -0
- package/dist/mention-input/mention-input.js +576 -0
- package/dist/mention-input/mention-input.js.map +1 -0
- package/dist/menu/menu.d.ts +2 -0
- package/dist/menu/menu.js +23 -12
- package/dist/menu/menu.js.map +1 -1
- package/dist/money-input/index.d.ts +2 -0
- package/dist/money-input/index.js +2 -0
- package/dist/money-input/money-input.d.ts +65 -0
- package/dist/money-input/money-input.js +607 -0
- package/dist/money-input/money-input.js.map +1 -0
- package/dist/morph-select/index.d.ts +2 -0
- package/dist/morph-select/index.js +2 -0
- package/dist/morph-select/morph-select.d.ts +71 -0
- package/dist/morph-select/morph-select.js +693 -0
- package/dist/morph-select/morph-select.js.map +1 -0
- package/dist/multi-select/index.d.ts +2 -0
- package/dist/multi-select/index.js +2 -0
- package/dist/multi-select/multi-select.d.ts +36 -0
- package/dist/multi-select/multi-select.js +152 -0
- package/dist/multi-select/multi-select.js.map +1 -0
- package/dist/number-field/index.d.ts +2 -0
- package/dist/number-field/index.js +2 -0
- package/dist/number-field/number-field.d.ts +42 -0
- package/dist/number-field/number-field.js +199 -0
- package/dist/number-field/number-field.js.map +1 -0
- package/dist/onboarding-checklist/index.d.ts +2 -0
- package/dist/onboarding-checklist/index.js +2 -0
- package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
- package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
- package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
- package/dist/password-field/password-field.js +2 -2
- package/dist/password-field/password-field.js.map +1 -1
- package/dist/phone-input/index.d.ts +3 -0
- package/dist/phone-input/index.js +3 -0
- package/dist/phone-input/phone-countries.d.ts +29 -0
- package/dist/phone-input/phone-countries.js +170 -0
- package/dist/phone-input/phone-countries.js.map +1 -0
- package/dist/phone-input/phone-input.d.ts +66 -0
- package/dist/phone-input/phone-input.js +440 -0
- package/dist/phone-input/phone-input.js.map +1 -0
- package/dist/popover/popover.d.ts +12 -0
- package/dist/popover/popover.js +31 -7
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.js +1 -1
- package/dist/radio-group/radio-group.js +1 -1
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/reactions/index.d.ts +2 -0
- package/dist/reactions/index.js +2 -0
- package/dist/reactions/reactions.d.ts +50 -0
- package/dist/reactions/reactions.js +315 -0
- package/dist/reactions/reactions.js.map +1 -0
- package/dist/rich-text-editor/index.d.ts +3 -0
- package/dist/rich-text-editor/index.js +2 -0
- package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
- package/dist/rich-text-editor/rich-text-editor.js +909 -0
- package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
- package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
- package/dist/rich-text-editor/rich-text-engine.js +366 -0
- package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
- package/dist/rich-text-editor/rich-text-html.js +174 -0
- package/dist/rich-text-editor/rich-text-html.js.map +1 -0
- package/dist/rich-text-editor/rich-text-icons.js +43 -0
- package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
- package/dist/ridgeline/index.d.ts +2 -0
- package/dist/ridgeline/index.js +2 -0
- package/dist/ridgeline/ridgeline.d.ts +55 -0
- package/dist/ridgeline/ridgeline.js +701 -0
- package/dist/ridgeline/ridgeline.js.map +1 -0
- package/dist/scroll-area/index.d.ts +2 -2
- package/dist/scroll-area/index.js +2 -2
- package/dist/scroll-area/scroll-area.d.ts +68 -31
- package/dist/scroll-area/scroll-area.js +208 -59
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/scrub-input/evaluate.d.ts +14 -0
- package/dist/scrub-input/evaluate.js +72 -0
- package/dist/scrub-input/evaluate.js.map +1 -0
- package/dist/scrub-input/index.d.ts +3 -0
- package/dist/scrub-input/index.js +3 -0
- package/dist/scrub-input/scrub-input.d.ts +74 -0
- package/dist/scrub-input/scrub-input.js +486 -0
- package/dist/scrub-input/scrub-input.js.map +1 -0
- package/dist/search-field/search-field.d.ts +2 -1
- package/dist/search-field/search-field.js +4 -4
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +20 -8
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +1 -1
- package/dist/sheet/sheet.js +2 -1
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/shortcut-recorder/index.d.ts +3 -0
- package/dist/shortcut-recorder/index.js +3 -0
- package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
- package/dist/shortcut-recorder/shortcut-model.js +206 -0
- package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
- package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
- package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
- package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
- package/dist/slider/index.d.ts +2 -2
- package/dist/slider/slider.d.ts +46 -14
- package/dist/slider/slider.js +151 -67
- package/dist/slider/slider.js.map +1 -1
- package/dist/slope-chart/index.d.ts +2 -0
- package/dist/slope-chart/index.js +2 -0
- package/dist/slope-chart/slope-chart.d.ts +54 -0
- package/dist/slope-chart/slope-chart.js +568 -0
- package/dist/slope-chart/slope-chart.js.map +1 -0
- package/dist/sparkline/index.d.ts +2 -0
- package/dist/sparkline/index.js +2 -0
- package/dist/sparkline/sparkline.d.ts +46 -0
- package/dist/sparkline/sparkline.js +390 -0
- package/dist/sparkline/sparkline.js.map +1 -0
- package/dist/split-bar/index.d.ts +2 -0
- package/dist/split-bar/index.js +2 -0
- package/dist/split-bar/split-bar.d.ts +62 -0
- package/dist/split-bar/split-bar.js +513 -0
- package/dist/split-bar/split-bar.js.map +1 -0
- package/dist/split-button/split-button.d.ts +1 -1
- package/dist/split-button/split-button.js +12 -11
- package/dist/split-button/split-button.js.map +1 -1
- package/dist/stat-card/index.d.ts +2 -2
- package/dist/stat-card/index.js +2 -2
- package/dist/stat-card/stat-card.d.ts +24 -4
- package/dist/stat-card/stat-card.js +114 -17
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/stepper/index.d.ts +2 -0
- package/dist/stepper/index.js +2 -0
- package/dist/stepper/stepper.d.ts +51 -0
- package/dist/stepper/stepper.js +215 -0
- package/dist/stepper/stepper.js.map +1 -0
- package/dist/streamgraph/index.d.ts +2 -0
- package/dist/streamgraph/index.js +2 -0
- package/dist/streamgraph/streamgraph.d.ts +70 -0
- package/dist/streamgraph/streamgraph.js +613 -0
- package/dist/streamgraph/streamgraph.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/swipe-actions/index.d.ts +2 -0
- package/dist/swipe-actions/index.js +2 -0
- package/dist/swipe-actions/swipe-actions.d.ts +67 -0
- package/dist/swipe-actions/swipe-actions.js +552 -0
- package/dist/swipe-actions/swipe-actions.js.map +1 -0
- package/dist/switch/switch.js +2 -2
- package/dist/switch/switch.js.map +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +56 -4
- package/dist/table/table.js +340 -165
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.js +2 -2
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/tag-input/index.d.ts +2 -0
- package/dist/tag-input/index.js +2 -0
- package/dist/tag-input/tag-input.d.ts +40 -0
- package/dist/tag-input/tag-input.js +269 -0
- package/dist/tag-input/tag-input.js.map +1 -0
- package/dist/text-morph/index.d.ts +2 -0
- package/dist/text-morph/index.js +2 -0
- package/dist/text-morph/text-morph.d.ts +23 -0
- package/dist/text-morph/text-morph.js +21 -0
- package/dist/text-morph/text-morph.js.map +1 -0
- package/dist/text-reveal/index.d.ts +2 -0
- package/dist/text-reveal/index.js +2 -0
- package/dist/text-reveal/text-reveal.d.ts +27 -0
- package/dist/text-reveal/text-reveal.js +51 -0
- package/dist/text-reveal/text-reveal.js.map +1 -0
- package/dist/text-shimmer/index.d.ts +2 -0
- package/dist/text-shimmer/index.js +2 -0
- package/dist/text-shimmer/text-shimmer.d.ts +29 -0
- package/dist/text-shimmer/text-shimmer.js +29 -0
- package/dist/text-shimmer/text-shimmer.js.map +1 -0
- package/dist/time-picker/time-picker.js +2 -2
- package/dist/time-picker/time-picker.js.map +1 -1
- package/dist/toast/index.d.ts +2 -2
- package/dist/toast/index.js +2 -2
- package/dist/toast/toast.d.ts +6 -0
- package/dist/toast/toast.js +82 -40
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +9 -2
- package/dist/tooltip/tooltip.js +7 -7
- package/dist/tooltip/tooltip.js.map +1 -1
- package/dist/treemap/index.d.ts +2 -0
- package/dist/treemap/index.js +2 -0
- package/dist/treemap/treemap.d.ts +53 -0
- package/dist/treemap/treemap.js +699 -0
- package/dist/treemap/treemap.js.map +1 -0
- package/dist/usage-meter/index.d.ts +2 -0
- package/dist/usage-meter/index.js +2 -0
- package/dist/usage-meter/usage-meter.d.ts +50 -0
- package/dist/usage-meter/usage-meter.js +440 -0
- package/dist/usage-meter/usage-meter.js.map +1 -0
- package/dist/waffle-chart/index.d.ts +2 -0
- package/dist/waffle-chart/index.js +2 -0
- package/dist/waffle-chart/waffle-chart.d.ts +57 -0
- package/dist/waffle-chart/waffle-chart.js +426 -0
- package/dist/waffle-chart/waffle-chart.js.map +1 -0
- package/package.json +165 -3
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useCallback, useRef } from "react";
|
|
2
|
+
//#region src/lib/menu-highlight.ts
|
|
3
|
+
const ROWS = "[role=\"menuitem\"], [role=\"menuitemradio\"], [role=\"menuitemcheckbox\"]";
|
|
4
|
+
/**
|
|
5
|
+
* One highlight for a whole menu, drawn by the menu's ::before (the
|
|
6
|
+
* `menu-highlight` utility) and moved from here: it appears in place on the
|
|
7
|
+
* first row, glides between rows after that (pointer and arrow keys alike,
|
|
8
|
+
* because React Aria focuses the row either way), and fades out shortly after
|
|
9
|
+
* focus leaves the rows. A row marked `data-destructive` tints it red. Returns
|
|
10
|
+
* the cleanup.
|
|
11
|
+
*/
|
|
12
|
+
function bindMenuHighlight(menu) {
|
|
13
|
+
let shown = false;
|
|
14
|
+
let timer = 0;
|
|
15
|
+
const hide = () => {
|
|
16
|
+
menu.removeAttribute("data-hl");
|
|
17
|
+
menu.removeAttribute("data-glide");
|
|
18
|
+
shown = false;
|
|
19
|
+
};
|
|
20
|
+
const onFocusIn = (event) => {
|
|
21
|
+
window.clearTimeout(timer);
|
|
22
|
+
const row = event.target.closest(ROWS);
|
|
23
|
+
if (!row || !menu.contains(row) || row.hasAttribute("data-disabled")) {
|
|
24
|
+
timer = window.setTimeout(hide, 70);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (shown) menu.setAttribute("data-glide", "");
|
|
28
|
+
else menu.removeAttribute("data-glide");
|
|
29
|
+
menu.style.setProperty("--hl-y", row.offsetTop + "px");
|
|
30
|
+
menu.style.setProperty("--hl-h", row.offsetHeight + "px");
|
|
31
|
+
if (row.hasAttribute("data-destructive")) menu.setAttribute("data-hl-tone", "destructive");
|
|
32
|
+
else menu.removeAttribute("data-hl-tone");
|
|
33
|
+
menu.setAttribute("data-hl", "");
|
|
34
|
+
shown = true;
|
|
35
|
+
};
|
|
36
|
+
const onFocusOut = (event) => {
|
|
37
|
+
if (menu.contains(event.relatedTarget)) return;
|
|
38
|
+
window.clearTimeout(timer);
|
|
39
|
+
hide();
|
|
40
|
+
};
|
|
41
|
+
menu.addEventListener("focusin", onFocusIn);
|
|
42
|
+
menu.addEventListener("focusout", onFocusOut);
|
|
43
|
+
return () => {
|
|
44
|
+
window.clearTimeout(timer);
|
|
45
|
+
menu.removeEventListener("focusin", onFocusIn);
|
|
46
|
+
menu.removeEventListener("focusout", onFocusOut);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A ref callback for the element that carries `menu-highlight`. A menu that
|
|
51
|
+
* mounts only while open (a popover) binds when it appears and unbinds when it
|
|
52
|
+
* goes, so this is a callback ref rather than an effect.
|
|
53
|
+
*/
|
|
54
|
+
function useMenuHighlight() {
|
|
55
|
+
const unbind = useRef(void 0);
|
|
56
|
+
return useCallback((node) => {
|
|
57
|
+
unbind.current?.();
|
|
58
|
+
unbind.current = node ? bindMenuHighlight(node) : void 0;
|
|
59
|
+
}, []);
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { useMenuHighlight };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=menu-highlight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu-highlight.js","names":[],"sources":["../../src/lib/menu-highlight.ts"],"sourcesContent":["import { useCallback, useRef } from \"react\";\n\nconst ROWS = '[role=\"menuitem\"], [role=\"menuitemradio\"], [role=\"menuitemcheckbox\"]';\n\n/**\n * One highlight for a whole menu, drawn by the menu's ::before (the\n * `menu-highlight` utility) and moved from here: it appears in place on the\n * first row, glides between rows after that (pointer and arrow keys alike,\n * because React Aria focuses the row either way), and fades out shortly after\n * focus leaves the rows. A row marked `data-destructive` tints it red. Returns\n * the cleanup.\n */\nfunction bindMenuHighlight(menu: HTMLElement) {\n let shown = false;\n let timer = 0;\n const hide = () => {\n menu.removeAttribute(\"data-hl\");\n menu.removeAttribute(\"data-glide\");\n shown = false;\n };\n const onFocusIn = (event: FocusEvent) => {\n window.clearTimeout(timer);\n const row = (event.target as Element).closest<HTMLElement>(ROWS);\n // Between rows (a gap, the separator, the menu itself) it waits a moment, so it can glide across.\n if (!row || !menu.contains(row) || row.hasAttribute(\"data-disabled\")) {\n timer = window.setTimeout(hide, 70);\n return;\n }\n if (shown) menu.setAttribute(\"data-glide\", \"\");\n else menu.removeAttribute(\"data-glide\");\n menu.style.setProperty(\"--hl-y\", row.offsetTop + \"px\");\n menu.style.setProperty(\"--hl-h\", row.offsetHeight + \"px\");\n if (row.hasAttribute(\"data-destructive\")) menu.setAttribute(\"data-hl-tone\", \"destructive\");\n else menu.removeAttribute(\"data-hl-tone\");\n menu.setAttribute(\"data-hl\", \"\");\n shown = true;\n };\n const onFocusOut = (event: FocusEvent) => {\n if (menu.contains(event.relatedTarget as Node | null)) return;\n window.clearTimeout(timer);\n hide();\n };\n menu.addEventListener(\"focusin\", onFocusIn);\n menu.addEventListener(\"focusout\", onFocusOut);\n return () => {\n window.clearTimeout(timer);\n menu.removeEventListener(\"focusin\", onFocusIn);\n menu.removeEventListener(\"focusout\", onFocusOut);\n };\n}\n\n/**\n * A ref callback for the element that carries `menu-highlight`. A menu that\n * mounts only while open (a popover) binds when it appears and unbinds when it\n * goes, so this is a callback ref rather than an effect.\n */\nexport function useMenuHighlight() {\n const unbind = useRef<(() => void) | undefined>(undefined);\n return useCallback((node: HTMLElement | null) => {\n unbind.current?.();\n unbind.current = node ? bindMenuHighlight(node) : undefined;\n }, []);\n}\n"],"mappings":";;AAEA,MAAM,OAAO;;;;;;;;;AAUb,SAAS,kBAAkB,MAAmB;CAC5C,IAAI,QAAQ;CACZ,IAAI,QAAQ;CACZ,MAAM,aAAa;EACjB,KAAK,gBAAgB,SAAS;EAC9B,KAAK,gBAAgB,YAAY;EACjC,QAAQ;CACV;CACA,MAAM,aAAa,UAAsB;EACvC,OAAO,aAAa,KAAK;EACzB,MAAM,MAAO,MAAM,OAAmB,QAAqB,IAAI;EAE/D,IAAI,CAAC,OAAO,CAAC,KAAK,SAAS,GAAG,KAAK,IAAI,aAAa,eAAe,GAAG;GACpE,QAAQ,OAAO,WAAW,MAAM,EAAE;GAClC;EACF;EACA,IAAI,OAAO,KAAK,aAAa,cAAc,EAAE;OACxC,KAAK,gBAAgB,YAAY;EACtC,KAAK,MAAM,YAAY,UAAU,IAAI,YAAY,IAAI;EACrD,KAAK,MAAM,YAAY,UAAU,IAAI,eAAe,IAAI;EACxD,IAAI,IAAI,aAAa,kBAAkB,GAAG,KAAK,aAAa,gBAAgB,aAAa;OACpF,KAAK,gBAAgB,cAAc;EACxC,KAAK,aAAa,WAAW,EAAE;EAC/B,QAAQ;CACV;CACA,MAAM,cAAc,UAAsB;EACxC,IAAI,KAAK,SAAS,MAAM,aAA4B,GAAG;EACvD,OAAO,aAAa,KAAK;EACzB,KAAK;CACP;CACA,KAAK,iBAAiB,WAAW,SAAS;CAC1C,KAAK,iBAAiB,YAAY,UAAU;CAC5C,aAAa;EACX,OAAO,aAAa,KAAK;EACzB,KAAK,oBAAoB,WAAW,SAAS;EAC7C,KAAK,oBAAoB,YAAY,UAAU;CACjD;AACF;;;;;;AAOA,SAAgB,mBAAmB;CACjC,MAAM,SAAS,OAAiC,KAAA,CAAS;CACzD,OAAO,aAAa,SAA6B;EAC/C,OAAO,UAAU;EACjB,OAAO,UAAU,OAAO,kBAAkB,IAAI,IAAI,KAAA;CACpD,GAAG,CAAC,CAAC;AACP"}
|
package/dist/lib/motion.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
2
2
|
//#region src/lib/motion.ts
|
|
3
3
|
/** useLayoutEffect warns when rendered on the server in React 18, which this package supports; there it falls back to useEffect. */
|
|
4
4
|
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
5
5
|
const prefersReducedMotion = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
6
|
+
const subscribeReducedMotion = (notify) => {
|
|
7
|
+
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
8
|
+
query.addEventListener("change", notify);
|
|
9
|
+
return () => query.removeEventListener("change", notify);
|
|
10
|
+
};
|
|
11
|
+
/** Whether the visitor prefers reduced motion, kept current. False on the server and until hydrated. */
|
|
12
|
+
const useReducedMotion = () => useSyncExternalStore(subscribeReducedMotion, prefersReducedMotion, () => false);
|
|
6
13
|
/**
|
|
7
14
|
* Springs the slot to the natural width of the incoming label when it changes,
|
|
8
15
|
* so a new label never snaps the button's size. Only a resize within 700ms of a
|
|
@@ -157,6 +164,6 @@ function useAriaBusy(pending) {
|
|
|
157
164
|
return ref;
|
|
158
165
|
}
|
|
159
166
|
//#endregion
|
|
160
|
-
export { anchorsLayer, prefersReducedMotion, pressScaleFor, useAriaBusy, useExitBackstop, useIsomorphicLayoutEffect, useLeavingItems, useMorphWidth };
|
|
167
|
+
export { anchorsLayer, prefersReducedMotion, pressScaleFor, useAriaBusy, useExitBackstop, useIsomorphicLayoutEffect, useLeavingItems, useMorphWidth, useReducedMotion };
|
|
161
168
|
|
|
162
169
|
//# sourceMappingURL=motion.js.map
|
package/dist/lib/motion.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"motion.js","names":[],"sources":["../../src/lib/motion.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, type RefObject } from \"react\";\n\n/*\n * Motion helpers shared by every component with button-like motion: the press\n * depth, the width that springs to fit new content, and the backstop for an\n * exit animation that never ends. Plain React and CSS, no animation library;\n * the animations themselves are keyframes and transitions in\n * @seekalfred/styles motion.css and the button-* utilities in styles.css.\n */\n\n/** useLayoutEffect warns when rendered on the server in React 18, which this package supports; there it falls back to useEffect. */\nexport const useIsomorphicLayoutEffect =\n typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nexport const prefersReducedMotion = () =>\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\n/**\n * Springs the slot to the natural width of the incoming label when it changes,\n * so a new label never snaps the button's size. Only a resize within 700ms of a\n * label change morphs; any other resize (a late web font, a parent reflow),\n * the first measurement and reduced motion jump straight to the new width, so\n * nothing wobbles on first paint. data-morphing turns on the slot's width\n * transition and its clip-path (button-slot).\n */\nexport function useMorphWidth(content: RefObject<HTMLElement | null>, key: string, enabled = true) {\n const lastKey = useRef(key);\n const armedUntil = useRef(0);\n useIsomorphicLayoutEffect(() => {\n if (lastKey.current === key) return;\n lastKey.current = key;\n armedUntil.current = performance.now() + 700;\n }, [key]);\n useEffect(() => {\n const node = content.current;\n const slot = node?.parentElement;\n if (!enabled || !node || !slot || typeof ResizeObserver === \"undefined\") return;\n let measured = false;\n let settle: ReturnType<typeof setTimeout> | undefined;\n const done = () => {\n clearTimeout(settle);\n delete slot.dataset.morphing;\n };\n const onEnd = (event: TransitionEvent) => {\n if (event.target === slot && event.propertyName === \"width\") done();\n };\n slot.addEventListener(\"transitionend\", onEnd);\n const apply = (next: number, at: number) => {\n if (!next || !measured || prefersReducedMotion() || at > armedUntil.current) {\n measured = next > 0;\n slot.style.width = next ? next + \"px\" : \"\";\n done();\n return;\n }\n slot.dataset.morphing = \"\";\n slot.style.width = next + \"px\";\n // A morph that ends without a width transition (nothing to animate) would\n // leave the clip-path on; this is the backstop.\n clearTimeout(settle);\n settle = setTimeout(done, 900);\n };\n // The write is deferred a frame. Setting the slot's width resizes whatever\n // contains it, and if that is observed too (a segment in a button group) doing\n // it inside this notification reports a ResizeObserver loop error.\n let frame = 0;\n const observer = new ResizeObserver(([entry]) => {\n if (!entry) return;\n const next = entry.contentRect.width;\n const at = performance.now();\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() => apply(next, at));\n });\n observer.observe(node);\n return () => {\n cancelAnimationFrame(frame);\n observer.disconnect();\n slot.removeEventListener(\"transitionend\", onEnd);\n clearTimeout(settle);\n };\n }, [content, enabled]);\n}\n\n/**\n * Runs remove after a backstop delay, in case an exit animation never ends (a\n * consumer who disables animation). The delay must outlast the animation:\n * 600ms covers the quick exits, a spring that settles in 736ms needs more.\n */\nexport function useExitBackstop(active: boolean, remove: () => void, delay = 600) {\n useEffect(() => {\n if (!active) return;\n const timer = setTimeout(remove, delay);\n return () => clearTimeout(timer);\n }, [active, remove, delay]);\n}\n\n/** A press goes a little deeper on an icon button and a little less on a wide one, so every size reads as the same push. */\nexport function pressScaleFor(width: number) {\n return width > 220 ? \"0.985\" : width && width <= 48 ? \"0.96\" : \"0.97\";\n}\n\n/** True for a trigger that anchors a menu, popover or dialog; those never scale. */\nexport function anchorsLayer(props: object) {\n const { \"aria-haspopup\": popup, role, \"data-state\": state } = props as Record<string, unknown>;\n return (\n (popup !== undefined && popup !== false && popup !== \"false\") ||\n role === \"combobox\" ||\n state !== undefined\n );\n}\n\n/** Where a node sat inside its offset parent, in px. */\nexport interface Rect {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\nexport interface LeavingItem<T> {\n id: number;\n value: T;\n /** The place the node last occupied, so it can be popped out of flow right there. */\n rect: Rect;\n}\n\n/**\n * Keeps the outgoing version of something on screen while its replacement\n * enters, which CSS alone cannot do: a node React unmounts is gone.\n *\n * `key` says when it has changed. On a change the previous `value` joins\n * `leaving`, stamped with the place it last occupied (read from `measure`,\n * the CURRENT node, after every commit); the caller renders each leaving item\n * out of flow at that rect with an exit animation and calls `remove` when it\n * ends. `changed` turns true on the first change, so nothing animates on first\n * mount. It must be a layout effect: the old node's position is DOM state that\n * only exists until the next paint.\n */\nexport function useLeavingItems<T>(key: string, value: T, measure: () => HTMLElement | null) {\n const previous = useRef({ key, value });\n const rect = useRef<Rect>({ left: 0, top: 0, width: 0, height: 0 });\n const nextId = useRef(0);\n const [leaving, setLeaving] = useState<LeavingItem<T>[]>([]);\n const [changed, setChanged] = useState(false);\n\n // Declared before the effect below, which overwrites the record this reads.\n useIsomorphicLayoutEffect(() => {\n const old = previous.current;\n if (old.key === key) return;\n setLeaving((list) => [...list, { id: nextId.current++, value: old.value, rect: rect.current }]);\n setChanged(true);\n }, [key]);\n\n useIsomorphicLayoutEffect(() => {\n previous.current = { key, value };\n const node = measure();\n if (node)\n rect.current = {\n left: node.offsetLeft,\n top: node.offsetTop,\n width: node.offsetWidth,\n height: node.offsetHeight,\n };\n });\n\n const remove = useCallback(\n (id: number) => setLeaving((list) => list.filter((item) => item.id !== id)),\n [],\n );\n return { leaving, changed, remove };\n}\n\n/**\n * React Aria sets aria-disabled and data-pending on a pending button but\n * forwards no aria-busy, so a pending button was not announced as busy. Put the\n * returned ref on the button (merge it with any other); nothing else manages\n * this attribute on the element.\n */\nexport function useAriaBusy(pending: boolean) {\n const ref = useRef<HTMLElement | null>(null);\n useIsomorphicLayoutEffect(() => {\n if (pending) ref.current?.setAttribute(\"aria-busy\", \"true\");\n else ref.current?.removeAttribute(\"aria-busy\");\n }, [pending]);\n return ref;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"motion.js","names":[],"sources":["../../src/lib/motion.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore, type RefObject } from \"react\";\n\n/*\n * Motion helpers shared by every component with button-like motion: the press\n * depth, the width that springs to fit new content, and the backstop for an\n * exit animation that never ends. Plain React and CSS, no animation library;\n * the animations themselves are keyframes and transitions in\n * @seekalfred/styles motion.css and the button-* utilities in styles.css.\n */\n\n/** useLayoutEffect warns when rendered on the server in React 18, which this package supports; there it falls back to useEffect. */\nexport const useIsomorphicLayoutEffect =\n typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nexport const prefersReducedMotion = () =>\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\nconst subscribeReducedMotion = (notify: () => void) => {\n const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n query.addEventListener(\"change\", notify);\n return () => query.removeEventListener(\"change\", notify);\n};\n\n/** Whether the visitor prefers reduced motion, kept current. False on the server and until hydrated. */\nexport const useReducedMotion = () =>\n useSyncExternalStore(subscribeReducedMotion, prefersReducedMotion, () => false);\n\n/**\n * Springs the slot to the natural width of the incoming label when it changes,\n * so a new label never snaps the button's size. Only a resize within 700ms of a\n * label change morphs; any other resize (a late web font, a parent reflow),\n * the first measurement and reduced motion jump straight to the new width, so\n * nothing wobbles on first paint. data-morphing turns on the slot's width\n * transition and its clip-path (button-slot).\n */\nexport function useMorphWidth(content: RefObject<HTMLElement | null>, key: string, enabled = true) {\n const lastKey = useRef(key);\n const armedUntil = useRef(0);\n useIsomorphicLayoutEffect(() => {\n if (lastKey.current === key) return;\n lastKey.current = key;\n armedUntil.current = performance.now() + 700;\n }, [key]);\n useEffect(() => {\n const node = content.current;\n const slot = node?.parentElement;\n if (!enabled || !node || !slot || typeof ResizeObserver === \"undefined\") return;\n let measured = false;\n let settle: ReturnType<typeof setTimeout> | undefined;\n const done = () => {\n clearTimeout(settle);\n delete slot.dataset.morphing;\n };\n const onEnd = (event: TransitionEvent) => {\n if (event.target === slot && event.propertyName === \"width\") done();\n };\n slot.addEventListener(\"transitionend\", onEnd);\n const apply = (next: number, at: number) => {\n if (!next || !measured || prefersReducedMotion() || at > armedUntil.current) {\n measured = next > 0;\n slot.style.width = next ? next + \"px\" : \"\";\n done();\n return;\n }\n slot.dataset.morphing = \"\";\n slot.style.width = next + \"px\";\n // A morph that ends without a width transition (nothing to animate) would\n // leave the clip-path on; this is the backstop.\n clearTimeout(settle);\n settle = setTimeout(done, 900);\n };\n // The write is deferred a frame. Setting the slot's width resizes whatever\n // contains it, and if that is observed too (a segment in a button group) doing\n // it inside this notification reports a ResizeObserver loop error.\n let frame = 0;\n const observer = new ResizeObserver(([entry]) => {\n if (!entry) return;\n const next = entry.contentRect.width;\n const at = performance.now();\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() => apply(next, at));\n });\n observer.observe(node);\n return () => {\n cancelAnimationFrame(frame);\n observer.disconnect();\n slot.removeEventListener(\"transitionend\", onEnd);\n clearTimeout(settle);\n };\n }, [content, enabled]);\n}\n\n/**\n * Runs remove after a backstop delay, in case an exit animation never ends (a\n * consumer who disables animation). The delay must outlast the animation:\n * 600ms covers the quick exits, a spring that settles in 736ms needs more.\n */\nexport function useExitBackstop(active: boolean, remove: () => void, delay = 600) {\n useEffect(() => {\n if (!active) return;\n const timer = setTimeout(remove, delay);\n return () => clearTimeout(timer);\n }, [active, remove, delay]);\n}\n\n/** A press goes a little deeper on an icon button and a little less on a wide one, so every size reads as the same push. */\nexport function pressScaleFor(width: number) {\n return width > 220 ? \"0.985\" : width && width <= 48 ? \"0.96\" : \"0.97\";\n}\n\n/** True for a trigger that anchors a menu, popover or dialog; those never scale. */\nexport function anchorsLayer(props: object) {\n const { \"aria-haspopup\": popup, role, \"data-state\": state } = props as Record<string, unknown>;\n return (\n (popup !== undefined && popup !== false && popup !== \"false\") ||\n role === \"combobox\" ||\n state !== undefined\n );\n}\n\n/** Where a node sat inside its offset parent, in px. */\nexport interface Rect {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\nexport interface LeavingItem<T> {\n id: number;\n value: T;\n /** The place the node last occupied, so it can be popped out of flow right there. */\n rect: Rect;\n}\n\n/**\n * Keeps the outgoing version of something on screen while its replacement\n * enters, which CSS alone cannot do: a node React unmounts is gone.\n *\n * `key` says when it has changed. On a change the previous `value` joins\n * `leaving`, stamped with the place it last occupied (read from `measure`,\n * the CURRENT node, after every commit); the caller renders each leaving item\n * out of flow at that rect with an exit animation and calls `remove` when it\n * ends. `changed` turns true on the first change, so nothing animates on first\n * mount. It must be a layout effect: the old node's position is DOM state that\n * only exists until the next paint.\n */\nexport function useLeavingItems<T>(key: string, value: T, measure: () => HTMLElement | null) {\n const previous = useRef({ key, value });\n const rect = useRef<Rect>({ left: 0, top: 0, width: 0, height: 0 });\n const nextId = useRef(0);\n const [leaving, setLeaving] = useState<LeavingItem<T>[]>([]);\n const [changed, setChanged] = useState(false);\n\n // Declared before the effect below, which overwrites the record this reads.\n useIsomorphicLayoutEffect(() => {\n const old = previous.current;\n if (old.key === key) return;\n setLeaving((list) => [...list, { id: nextId.current++, value: old.value, rect: rect.current }]);\n setChanged(true);\n }, [key]);\n\n useIsomorphicLayoutEffect(() => {\n previous.current = { key, value };\n const node = measure();\n if (node)\n rect.current = {\n left: node.offsetLeft,\n top: node.offsetTop,\n width: node.offsetWidth,\n height: node.offsetHeight,\n };\n });\n\n const remove = useCallback(\n (id: number) => setLeaving((list) => list.filter((item) => item.id !== id)),\n [],\n );\n return { leaving, changed, remove };\n}\n\n/**\n * React Aria sets aria-disabled and data-pending on a pending button but\n * forwards no aria-busy, so a pending button was not announced as busy. Put the\n * returned ref on the button (merge it with any other); nothing else manages\n * this attribute on the element.\n */\nexport function useAriaBusy(pending: boolean) {\n const ref = useRef<HTMLElement | null>(null);\n useIsomorphicLayoutEffect(() => {\n if (pending) ref.current?.setAttribute(\"aria-busy\", \"true\");\n else ref.current?.removeAttribute(\"aria-busy\");\n }, [pending]);\n return ref;\n}\n"],"mappings":"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"}
|
|
@@ -19,7 +19,7 @@ function RollingValue({ srValue, swapKey, dir, instanceKey, children }) {
|
|
|
19
19
|
swapKey,
|
|
20
20
|
dir,
|
|
21
21
|
motion: "roll",
|
|
22
|
-
className: "
|
|
22
|
+
className: "flex items-center [&>span:not([aria-hidden])]:max-w-full [&>span:not([aria-hidden])]:truncate",
|
|
23
23
|
children
|
|
24
24
|
}, instanceKey)
|
|
25
25
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rolling-value.js","names":[],"sources":["../../src/lib/rolling-value.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nimport { SwapText } from \"./swap-text.js\";\n\n/**\n * The value shown in a picker's trigger. The caller's own text stays, visually\n * hidden, as what a screen reader reads (`srValue`); the copy the eye sees rolls\n * when it changes: a later value rises in from below, an earlier one drops in from\n * above, and the old one lifts away. Nothing animates on first mount. Long text is\n * cut with an ellipsis. `instanceKey` remounts the swap, which is how a caller whose\n * first value arrives a render late makes that arrival the first mount.\n */\nexport function RollingValue({\n srValue,\n swapKey,\n dir,\n instanceKey,\n children,\n}: {\n srValue: ReactNode;\n swapKey: string;\n dir: number;\n instanceKey?: string;\n children: ReactNode;\n}) {\n return (\n <span className=\"grid min-w-0 flex-1\">\n {srValue}\n <span aria-hidden=\"true\" className=\"min-w-0 overflow-hidden whitespace-nowrap\">\n <SwapText\n key={instanceKey}\n swapKey={swapKey}\n dir={dir}\n motion=\"roll\"\n className=\"
|
|
1
|
+
{"version":3,"file":"rolling-value.js","names":[],"sources":["../../src/lib/rolling-value.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nimport { SwapText } from \"./swap-text.js\";\n\n/**\n * The value shown in a picker's trigger. The caller's own text stays, visually\n * hidden, as what a screen reader reads (`srValue`); the copy the eye sees rolls\n * when it changes: a later value rises in from below, an earlier one drops in from\n * above, and the old one lifts away. Nothing animates on first mount. Long text is\n * cut with an ellipsis. `instanceKey` remounts the swap, which is how a caller whose\n * first value arrives a render late makes that arrival the first mount.\n */\nexport function RollingValue({\n srValue,\n swapKey,\n dir,\n instanceKey,\n children,\n}: {\n srValue: ReactNode;\n swapKey: string;\n dir: number;\n instanceKey?: string;\n children: ReactNode;\n}) {\n return (\n <span className=\"grid min-w-0 flex-1\">\n {srValue}\n <span aria-hidden=\"true\" className=\"min-w-0 overflow-hidden whitespace-nowrap\">\n <SwapText\n key={instanceKey}\n swapKey={swapKey}\n dir={dir}\n motion=\"roll\"\n // A flex row, not a block of inline boxes: a truncated inline-block sits on the\n // line's baseline, which stretches the line and leaves the text above centre.\n className=\"flex items-center [&>span:not([aria-hidden])]:max-w-full [&>span:not([aria-hidden])]:truncate\"\n >\n {children}\n </SwapText>\n </span>\n </span>\n );\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,aAAa,EAC3B,SACA,SACA,KACA,aACA,YAOC;CACD,OACE,qBAAC,QAAD;EAAM,WAAU;EAAhB,UAAA,CACG,SACD,oBAAC,QAAD;GAAM,eAAY;GAAO,WAAU;GACjC,UAAA,oBAAC,UAAD;IAEW;IACJ;IACL,QAAO;IAGP,WAAU;IAET;GACO,GATH,WASG;EACN,CAAA,CACF;;AAEV"}
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { prefersReducedMotion } from "./motion.js";
|
|
2
|
+
//#region src/lib/spring-tween.ts
|
|
3
|
+
/** visualDuration .26s, bounce .12: presses, toggles, cursors and small indicators. */
|
|
4
|
+
const SNAPPY = {
|
|
5
|
+
curve: [
|
|
6
|
+
0,
|
|
7
|
+
.043,
|
|
8
|
+
.1426,
|
|
9
|
+
.2664,
|
|
10
|
+
.3943,
|
|
11
|
+
.5144,
|
|
12
|
+
.6206,
|
|
13
|
+
.7107,
|
|
14
|
+
.7845,
|
|
15
|
+
.8433,
|
|
16
|
+
.889,
|
|
17
|
+
.9236,
|
|
18
|
+
.9492,
|
|
19
|
+
.9678,
|
|
20
|
+
.9808,
|
|
21
|
+
.9897,
|
|
22
|
+
.9956,
|
|
23
|
+
.9992,
|
|
24
|
+
1.0014,
|
|
25
|
+
1.0025,
|
|
26
|
+
1.0029,
|
|
27
|
+
1
|
|
28
|
+
],
|
|
29
|
+
ms: 336
|
|
30
|
+
};
|
|
31
|
+
/** visualDuration .4s, no bounce: shapes and layout that move while on screen. */
|
|
32
|
+
const SMOOTH = {
|
|
33
|
+
curve: [
|
|
34
|
+
0,
|
|
35
|
+
.0191,
|
|
36
|
+
.0667,
|
|
37
|
+
.1313,
|
|
38
|
+
.2048,
|
|
39
|
+
.2816,
|
|
40
|
+
.3577,
|
|
41
|
+
.4308,
|
|
42
|
+
.4991,
|
|
43
|
+
.562,
|
|
44
|
+
.6189,
|
|
45
|
+
.67,
|
|
46
|
+
.7154,
|
|
47
|
+
.7554,
|
|
48
|
+
.7905,
|
|
49
|
+
.821,
|
|
50
|
+
.8475,
|
|
51
|
+
.8704,
|
|
52
|
+
.89,
|
|
53
|
+
.9069,
|
|
54
|
+
.9213,
|
|
55
|
+
.9336,
|
|
56
|
+
.9441,
|
|
57
|
+
.9529,
|
|
58
|
+
.9605,
|
|
59
|
+
.9668,
|
|
60
|
+
.9722,
|
|
61
|
+
.9767,
|
|
62
|
+
.9805,
|
|
63
|
+
.9837,
|
|
64
|
+
.9864,
|
|
65
|
+
.9887,
|
|
66
|
+
.9905,
|
|
67
|
+
.9921,
|
|
68
|
+
.9934,
|
|
69
|
+
.9945,
|
|
70
|
+
.9955,
|
|
71
|
+
.9962,
|
|
72
|
+
.9969,
|
|
73
|
+
.9974,
|
|
74
|
+
.9978,
|
|
75
|
+
.9982,
|
|
76
|
+
.9985,
|
|
77
|
+
.9988,
|
|
78
|
+
.999,
|
|
79
|
+
1
|
|
80
|
+
],
|
|
81
|
+
ms: 720
|
|
82
|
+
};
|
|
83
|
+
/** visualDuration .72s, no bounce: a first sweep that opens slowly and settles long. */
|
|
84
|
+
const REVEAL = {
|
|
85
|
+
curve: [
|
|
86
|
+
0,
|
|
87
|
+
.0063,
|
|
88
|
+
.0232,
|
|
89
|
+
.0484,
|
|
90
|
+
.0799,
|
|
91
|
+
.1159,
|
|
92
|
+
.1552,
|
|
93
|
+
.1964,
|
|
94
|
+
.2388,
|
|
95
|
+
.2816,
|
|
96
|
+
.3242,
|
|
97
|
+
.366,
|
|
98
|
+
.4069,
|
|
99
|
+
.4464,
|
|
100
|
+
.4844,
|
|
101
|
+
.5207,
|
|
102
|
+
.5553,
|
|
103
|
+
.588,
|
|
104
|
+
.6189,
|
|
105
|
+
.648,
|
|
106
|
+
.6753,
|
|
107
|
+
.7009,
|
|
108
|
+
.7248,
|
|
109
|
+
.747,
|
|
110
|
+
.7676,
|
|
111
|
+
.7868,
|
|
112
|
+
.8046,
|
|
113
|
+
.821,
|
|
114
|
+
.8362,
|
|
115
|
+
.8502,
|
|
116
|
+
.8631,
|
|
117
|
+
.875,
|
|
118
|
+
.8859,
|
|
119
|
+
.8959,
|
|
120
|
+
.9052,
|
|
121
|
+
.9136,
|
|
122
|
+
.9213,
|
|
123
|
+
.9284,
|
|
124
|
+
.9349,
|
|
125
|
+
.9408,
|
|
126
|
+
.9462,
|
|
127
|
+
.9511,
|
|
128
|
+
.9556,
|
|
129
|
+
.9597,
|
|
130
|
+
.9634,
|
|
131
|
+
.9668,
|
|
132
|
+
.9699,
|
|
133
|
+
.9727,
|
|
134
|
+
.9753,
|
|
135
|
+
.9776,
|
|
136
|
+
.9797,
|
|
137
|
+
.9816,
|
|
138
|
+
.9834,
|
|
139
|
+
.985,
|
|
140
|
+
.9864,
|
|
141
|
+
.9877,
|
|
142
|
+
.9889,
|
|
143
|
+
.9899,
|
|
144
|
+
.9909,
|
|
145
|
+
.9918,
|
|
146
|
+
.9926,
|
|
147
|
+
.9933,
|
|
148
|
+
.994,
|
|
149
|
+
.9945,
|
|
150
|
+
.9951,
|
|
151
|
+
.9956,
|
|
152
|
+
.996,
|
|
153
|
+
.9964,
|
|
154
|
+
.9967,
|
|
155
|
+
.9971,
|
|
156
|
+
.9973,
|
|
157
|
+
.9976,
|
|
158
|
+
.9978,
|
|
159
|
+
.9981,
|
|
160
|
+
.9982,
|
|
161
|
+
.9984,
|
|
162
|
+
1
|
|
163
|
+
],
|
|
164
|
+
ms: 1232
|
|
165
|
+
};
|
|
166
|
+
/** visualDuration .5s, no bounce: parts of a whole moving to a new share. */
|
|
167
|
+
const SETTLE = {
|
|
168
|
+
curve: [
|
|
169
|
+
0,
|
|
170
|
+
.0126,
|
|
171
|
+
.045,
|
|
172
|
+
.091,
|
|
173
|
+
.1455,
|
|
174
|
+
.2048,
|
|
175
|
+
.2662,
|
|
176
|
+
.3275,
|
|
177
|
+
.3874,
|
|
178
|
+
.4448,
|
|
179
|
+
.4991,
|
|
180
|
+
.5499,
|
|
181
|
+
.5969,
|
|
182
|
+
.6401,
|
|
183
|
+
.6796,
|
|
184
|
+
.7154,
|
|
185
|
+
.7478,
|
|
186
|
+
.777,
|
|
187
|
+
.8032,
|
|
188
|
+
.8266,
|
|
189
|
+
.8475,
|
|
190
|
+
.8661,
|
|
191
|
+
.8825,
|
|
192
|
+
.8971,
|
|
193
|
+
.91,
|
|
194
|
+
.9213,
|
|
195
|
+
.9313,
|
|
196
|
+
.9401,
|
|
197
|
+
.9478,
|
|
198
|
+
.9545,
|
|
199
|
+
.9605,
|
|
200
|
+
.9656,
|
|
201
|
+
.9701,
|
|
202
|
+
.9741,
|
|
203
|
+
.9775,
|
|
204
|
+
.9805,
|
|
205
|
+
.9831,
|
|
206
|
+
.9854,
|
|
207
|
+
.9873,
|
|
208
|
+
.9891,
|
|
209
|
+
.9905,
|
|
210
|
+
.9918,
|
|
211
|
+
.9929,
|
|
212
|
+
.9939,
|
|
213
|
+
.9947,
|
|
214
|
+
.9955,
|
|
215
|
+
.9961,
|
|
216
|
+
.9966,
|
|
217
|
+
.9971,
|
|
218
|
+
.9975,
|
|
219
|
+
.9978,
|
|
220
|
+
.9981,
|
|
221
|
+
.9984,
|
|
222
|
+
.9986,
|
|
223
|
+
.9988,
|
|
224
|
+
1
|
|
225
|
+
],
|
|
226
|
+
ms: 880
|
|
227
|
+
};
|
|
228
|
+
/** visualDuration .42s, bounce .16: things that grow into place (a bar, a width that follows new content). */
|
|
229
|
+
const MORPH = {
|
|
230
|
+
curve: [
|
|
231
|
+
0,
|
|
232
|
+
.0178,
|
|
233
|
+
.0636,
|
|
234
|
+
.1279,
|
|
235
|
+
.2033,
|
|
236
|
+
.2841,
|
|
237
|
+
.366,
|
|
238
|
+
.4459,
|
|
239
|
+
.5218,
|
|
240
|
+
.5921,
|
|
241
|
+
.6562,
|
|
242
|
+
.7136,
|
|
243
|
+
.7643,
|
|
244
|
+
.8086,
|
|
245
|
+
.8466,
|
|
246
|
+
.879,
|
|
247
|
+
.9063,
|
|
248
|
+
.9289,
|
|
249
|
+
.9475,
|
|
250
|
+
.9626,
|
|
251
|
+
.9746,
|
|
252
|
+
.9841,
|
|
253
|
+
.9914,
|
|
254
|
+
.9969,
|
|
255
|
+
1.001,
|
|
256
|
+
1.0038,
|
|
257
|
+
1.0058,
|
|
258
|
+
1.0069,
|
|
259
|
+
1.0075,
|
|
260
|
+
1.0077,
|
|
261
|
+
1.0076,
|
|
262
|
+
1.0072,
|
|
263
|
+
1.0067,
|
|
264
|
+
1.0062,
|
|
265
|
+
1.0055,
|
|
266
|
+
1.0049,
|
|
267
|
+
1.0043,
|
|
268
|
+
1.0037,
|
|
269
|
+
1.0032,
|
|
270
|
+
1.0027,
|
|
271
|
+
1.0022,
|
|
272
|
+
1.0018,
|
|
273
|
+
1.0015,
|
|
274
|
+
1.0012,
|
|
275
|
+
1.0009,
|
|
276
|
+
1.0007,
|
|
277
|
+
1
|
|
278
|
+
],
|
|
279
|
+
ms: 736
|
|
280
|
+
};
|
|
281
|
+
/** visualDuration .9s, no bounce: the stream swells out of its centre the first time it is seen. */
|
|
282
|
+
const SWELL = {
|
|
283
|
+
curve: [
|
|
284
|
+
0,
|
|
285
|
+
.0041,
|
|
286
|
+
.0153,
|
|
287
|
+
.0324,
|
|
288
|
+
.0543,
|
|
289
|
+
.0799,
|
|
290
|
+
.1084,
|
|
291
|
+
.1392,
|
|
292
|
+
.1715,
|
|
293
|
+
.2048,
|
|
294
|
+
.2388,
|
|
295
|
+
.273,
|
|
296
|
+
.3072,
|
|
297
|
+
.341,
|
|
298
|
+
.3743,
|
|
299
|
+
.4069,
|
|
300
|
+
.4386,
|
|
301
|
+
.4694,
|
|
302
|
+
.4991,
|
|
303
|
+
.5278,
|
|
304
|
+
.5553,
|
|
305
|
+
.5816,
|
|
306
|
+
.6068,
|
|
307
|
+
.6308,
|
|
308
|
+
.6536,
|
|
309
|
+
.6753,
|
|
310
|
+
.6959,
|
|
311
|
+
.7154,
|
|
312
|
+
.7338,
|
|
313
|
+
.7512,
|
|
314
|
+
.7676,
|
|
315
|
+
.7831,
|
|
316
|
+
.7976,
|
|
317
|
+
.8113,
|
|
318
|
+
.8242,
|
|
319
|
+
.8362,
|
|
320
|
+
.8475,
|
|
321
|
+
.8581,
|
|
322
|
+
.868,
|
|
323
|
+
.8773,
|
|
324
|
+
.8859,
|
|
325
|
+
.894,
|
|
326
|
+
.9016,
|
|
327
|
+
.9086,
|
|
328
|
+
.9152,
|
|
329
|
+
.9213,
|
|
330
|
+
.927,
|
|
331
|
+
.9323,
|
|
332
|
+
.9373,
|
|
333
|
+
.9419,
|
|
334
|
+
.9462,
|
|
335
|
+
.9501,
|
|
336
|
+
.9538,
|
|
337
|
+
.9573,
|
|
338
|
+
.9605,
|
|
339
|
+
.9634,
|
|
340
|
+
.9662,
|
|
341
|
+
.9687,
|
|
342
|
+
.9711,
|
|
343
|
+
.9733,
|
|
344
|
+
.9753,
|
|
345
|
+
.9772,
|
|
346
|
+
.9789,
|
|
347
|
+
.9805,
|
|
348
|
+
.982,
|
|
349
|
+
.9834,
|
|
350
|
+
.9847,
|
|
351
|
+
.9858,
|
|
352
|
+
.9869,
|
|
353
|
+
.9879,
|
|
354
|
+
.9889,
|
|
355
|
+
.9897,
|
|
356
|
+
.9905,
|
|
357
|
+
.9913,
|
|
358
|
+
.992,
|
|
359
|
+
.9926,
|
|
360
|
+
.9932,
|
|
361
|
+
.9937,
|
|
362
|
+
.9942,
|
|
363
|
+
.9947,
|
|
364
|
+
.9951,
|
|
365
|
+
.9955,
|
|
366
|
+
.9958,
|
|
367
|
+
.9962,
|
|
368
|
+
.9965,
|
|
369
|
+
.9967,
|
|
370
|
+
.997,
|
|
371
|
+
.9972,
|
|
372
|
+
.9975,
|
|
373
|
+
.9977,
|
|
374
|
+
.9978,
|
|
375
|
+
.998,
|
|
376
|
+
1
|
|
377
|
+
],
|
|
378
|
+
ms: 1472
|
|
379
|
+
};
|
|
380
|
+
/** visualDuration .58s, no bounce: segments of a meter growing into place once seen. */
|
|
381
|
+
const FILL = {
|
|
382
|
+
curve: [
|
|
383
|
+
0,
|
|
384
|
+
.0095,
|
|
385
|
+
.0345,
|
|
386
|
+
.0707,
|
|
387
|
+
.1146,
|
|
388
|
+
.1636,
|
|
389
|
+
.2153,
|
|
390
|
+
.2683,
|
|
391
|
+
.3212,
|
|
392
|
+
.3732,
|
|
393
|
+
.4234,
|
|
394
|
+
.4715,
|
|
395
|
+
.517,
|
|
396
|
+
.5599,
|
|
397
|
+
.6,
|
|
398
|
+
.6372,
|
|
399
|
+
.6717,
|
|
400
|
+
.7034,
|
|
401
|
+
.7326,
|
|
402
|
+
.7593,
|
|
403
|
+
.7836,
|
|
404
|
+
.8058,
|
|
405
|
+
.8259,
|
|
406
|
+
.8441,
|
|
407
|
+
.8605,
|
|
408
|
+
.8754,
|
|
409
|
+
.8888,
|
|
410
|
+
.9008,
|
|
411
|
+
.9116,
|
|
412
|
+
.9213,
|
|
413
|
+
.93,
|
|
414
|
+
.9378,
|
|
415
|
+
.9447,
|
|
416
|
+
.9509,
|
|
417
|
+
.9565,
|
|
418
|
+
.9614,
|
|
419
|
+
.9658,
|
|
420
|
+
.9697,
|
|
421
|
+
.9732,
|
|
422
|
+
.9763,
|
|
423
|
+
.979,
|
|
424
|
+
.9815,
|
|
425
|
+
.9836,
|
|
426
|
+
.9855,
|
|
427
|
+
.9872,
|
|
428
|
+
.9887,
|
|
429
|
+
.9901,
|
|
430
|
+
.9912,
|
|
431
|
+
.9923,
|
|
432
|
+
.9932,
|
|
433
|
+
.994,
|
|
434
|
+
.9947,
|
|
435
|
+
.9953,
|
|
436
|
+
.9959,
|
|
437
|
+
.9964,
|
|
438
|
+
.9968,
|
|
439
|
+
.9972,
|
|
440
|
+
.9975,
|
|
441
|
+
.9978,
|
|
442
|
+
.9981,
|
|
443
|
+
.9983,
|
|
444
|
+
.9985,
|
|
445
|
+
.9987,
|
|
446
|
+
1
|
|
447
|
+
],
|
|
448
|
+
ms: 1008
|
|
449
|
+
};
|
|
450
|
+
const sample = ({ curve, ms }, t) => {
|
|
451
|
+
const at = t * ms / 16;
|
|
452
|
+
const i = Math.min(Math.floor(at), curve.length - 2);
|
|
453
|
+
return curve[i] + (curve[i + 1] - curve[i]) * (at - i);
|
|
454
|
+
};
|
|
455
|
+
/**
|
|
456
|
+
* Runs `onFrame` with spring progress (0 to 1, may overshoot a little) every
|
|
457
|
+
* frame until it settles (after `delay` ms), then `onDone`. Returns a cancel function. Under
|
|
458
|
+
* reduced motion it jumps: one frame at 1, then done.
|
|
459
|
+
*/
|
|
460
|
+
function tween(spring, onFrame, onDone, delay = 0) {
|
|
461
|
+
if (prefersReducedMotion()) {
|
|
462
|
+
onFrame(1);
|
|
463
|
+
onDone?.();
|
|
464
|
+
return () => {};
|
|
465
|
+
}
|
|
466
|
+
const start = performance.now() + delay;
|
|
467
|
+
let frame = 0;
|
|
468
|
+
const tick = (now) => {
|
|
469
|
+
const t = Math.min(Math.max((now - start) / spring.ms, 0), 1);
|
|
470
|
+
onFrame(t < 1 ? sample(spring, t) : 1);
|
|
471
|
+
if (t < 1) frame = requestAnimationFrame(tick);
|
|
472
|
+
else onDone?.();
|
|
473
|
+
};
|
|
474
|
+
frame = requestAnimationFrame(tick);
|
|
475
|
+
return () => cancelAnimationFrame(frame);
|
|
476
|
+
}
|
|
477
|
+
const live = (value) => ({
|
|
478
|
+
value,
|
|
479
|
+
stop: () => {}
|
|
480
|
+
});
|
|
481
|
+
/**
|
|
482
|
+
* Moves `target` to `to` on `spring`, starting from wherever it is now, so a
|
|
483
|
+
* change mid-flight never jumps. `onFrame` repaints; `done` runs when it settles.
|
|
484
|
+
*/
|
|
485
|
+
function retarget(target, to, spring, onFrame, delay = 0, done) {
|
|
486
|
+
target.stop();
|
|
487
|
+
const from = target.value;
|
|
488
|
+
if (from === to) {
|
|
489
|
+
done?.();
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
target.stop = tween(spring, (p) => {
|
|
493
|
+
target.value = from + (to - from) * p;
|
|
494
|
+
onFrame();
|
|
495
|
+
}, done, delay);
|
|
496
|
+
}
|
|
497
|
+
//#endregion
|
|
498
|
+
export { FILL, MORPH, REVEAL, SETTLE, SMOOTH, SNAPPY, SWELL, live, retarget, tween };
|
|
499
|
+
|
|
500
|
+
//# sourceMappingURL=spring-tween.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spring-tween.js","names":[],"sources":["../../src/lib/spring-tween.ts"],"sourcesContent":["import { prefersReducedMotion } from \"./motion.js\";\n\n/**\n * A spring sampled every 16ms from Motion's solver, for values a script has to\n * drive frame by frame (an SVG shape, a cursor riding a line) where a CSS\n * transition cannot reach. The same curves back the --ease-snappy and\n * --ease-smooth tokens, so scripted and CSS motion agree.\n */\nexport interface Spring {\n readonly curve: readonly number[];\n readonly ms: number;\n}\n\n/** visualDuration .26s, bounce .12: presses, toggles, cursors and small indicators. */\nexport const SNAPPY: Spring = {\n curve: [\n 0, 0.043, 0.1426, 0.2664, 0.3943, 0.5144, 0.6206, 0.7107, 0.7845, 0.8433, 0.889, 0.9236, 0.9492,\n 0.9678, 0.9808, 0.9897, 0.9956, 0.9992, 1.0014, 1.0025, 1.0029, 1,\n ],\n ms: 336,\n};\n\n/** visualDuration .4s, no bounce: shapes and layout that move while on screen. */\nexport const SMOOTH: Spring = {\n curve: [\n 0, 0.0191, 0.0667, 0.1313, 0.2048, 0.2816, 0.3577, 0.4308, 0.4991, 0.562, 0.6189, 0.67, 0.7154,\n 0.7554, 0.7905, 0.821, 0.8475, 0.8704, 0.89, 0.9069, 0.9213, 0.9336, 0.9441, 0.9529, 0.9605,\n 0.9668, 0.9722, 0.9767, 0.9805, 0.9837, 0.9864, 0.9887, 0.9905, 0.9921, 0.9934, 0.9945, 0.9955,\n 0.9962, 0.9969, 0.9974, 0.9978, 0.9982, 0.9985, 0.9988, 0.999, 1,\n ],\n ms: 720,\n};\n\n/** visualDuration .72s, no bounce: a first sweep that opens slowly and settles long. */\nexport const REVEAL: Spring = {\n curve: [\n 0, 0.0063, 0.0232, 0.0484, 0.0799, 0.1159, 0.1552, 0.1964, 0.2388, 0.2816, 0.3242, 0.366, 0.4069,\n 0.4464, 0.4844, 0.5207, 0.5553, 0.588, 0.6189, 0.648, 0.6753, 0.7009, 0.7248, 0.747, 0.7676,\n 0.7868, 0.8046, 0.821, 0.8362, 0.8502, 0.8631, 0.875, 0.8859, 0.8959, 0.9052, 0.9136, 0.9213,\n 0.9284, 0.9349, 0.9408, 0.9462, 0.9511, 0.9556, 0.9597, 0.9634, 0.9668, 0.9699, 0.9727, 0.9753,\n 0.9776, 0.9797, 0.9816, 0.9834, 0.985, 0.9864, 0.9877, 0.9889, 0.9899, 0.9909, 0.9918, 0.9926,\n 0.9933, 0.994, 0.9945, 0.9951, 0.9956, 0.996, 0.9964, 0.9967, 0.9971, 0.9973, 0.9976, 0.9978,\n 0.9981, 0.9982, 0.9984, 1,\n ],\n ms: 1232,\n};\n\n/** visualDuration .5s, no bounce: parts of a whole moving to a new share. */\nexport const SETTLE: Spring = {\n curve: [\n 0, 0.0126, 0.045, 0.091, 0.1455, 0.2048, 0.2662, 0.3275, 0.3874, 0.4448, 0.4991, 0.5499, 0.5969,\n 0.6401, 0.6796, 0.7154, 0.7478, 0.777, 0.8032, 0.8266, 0.8475, 0.8661, 0.8825, 0.8971, 0.91,\n 0.9213, 0.9313, 0.9401, 0.9478, 0.9545, 0.9605, 0.9656, 0.9701, 0.9741, 0.9775, 0.9805, 0.9831,\n 0.9854, 0.9873, 0.9891, 0.9905, 0.9918, 0.9929, 0.9939, 0.9947, 0.9955, 0.9961, 0.9966, 0.9971,\n 0.9975, 0.9978, 0.9981, 0.9984, 0.9986, 0.9988, 1,\n ],\n ms: 880,\n};\n\n/** visualDuration .42s, bounce .16: things that grow into place (a bar, a width that follows new content). */\nexport const MORPH: Spring = {\n curve: [\n 0, 0.0178, 0.0636, 0.1279, 0.2033, 0.2841, 0.366, 0.4459, 0.5218, 0.5921, 0.6562, 0.7136, 0.7643,\n 0.8086, 0.8466, 0.879, 0.9063, 0.9289, 0.9475, 0.9626, 0.9746, 0.9841, 0.9914, 0.9969, 1.001,\n 1.0038, 1.0058, 1.0069, 1.0075, 1.0077, 1.0076, 1.0072, 1.0067, 1.0062, 1.0055, 1.0049, 1.0043,\n 1.0037, 1.0032, 1.0027, 1.0022, 1.0018, 1.0015, 1.0012, 1.0009, 1.0007, 1,\n ],\n ms: 736,\n};\n\n/** visualDuration .9s, no bounce: the stream swells out of its centre the first time it is seen. */\nexport const SWELL: Spring = {\n curve: [\n 0, 0.0041, 0.0153, 0.0324, 0.0543, 0.0799, 0.1084, 0.1392, 0.1715, 0.2048, 0.2388, 0.273,\n 0.3072, 0.341, 0.3743, 0.4069, 0.4386, 0.4694, 0.4991, 0.5278, 0.5553, 0.5816, 0.6068, 0.6308,\n 0.6536, 0.6753, 0.6959, 0.7154, 0.7338, 0.7512, 0.7676, 0.7831, 0.7976, 0.8113, 0.8242, 0.8362,\n 0.8475, 0.8581, 0.868, 0.8773, 0.8859, 0.894, 0.9016, 0.9086, 0.9152, 0.9213, 0.927, 0.9323,\n 0.9373, 0.9419, 0.9462, 0.9501, 0.9538, 0.9573, 0.9605, 0.9634, 0.9662, 0.9687, 0.9711, 0.9733,\n 0.9753, 0.9772, 0.9789, 0.9805, 0.982, 0.9834, 0.9847, 0.9858, 0.9869, 0.9879, 0.9889, 0.9897,\n 0.9905, 0.9913, 0.992, 0.9926, 0.9932, 0.9937, 0.9942, 0.9947, 0.9951, 0.9955, 0.9958, 0.9962,\n 0.9965, 0.9967, 0.997, 0.9972, 0.9975, 0.9977, 0.9978, 0.998, 1,\n ],\n ms: 1472,\n};\n\n/** visualDuration .58s, no bounce: segments of a meter growing into place once seen. */\nexport const FILL: Spring = {\n curve: [\n 0, 0.0095, 0.0345, 0.0707, 0.1146, 0.1636, 0.2153, 0.2683, 0.3212, 0.3732, 0.4234, 0.4715, 0.517,\n 0.5599, 0.6, 0.6372, 0.6717, 0.7034, 0.7326, 0.7593, 0.7836, 0.8058, 0.8259, 0.8441, 0.8605,\n 0.8754, 0.8888, 0.9008, 0.9116, 0.9213, 0.93, 0.9378, 0.9447, 0.9509, 0.9565, 0.9614, 0.9658,\n 0.9697, 0.9732, 0.9763, 0.979, 0.9815, 0.9836, 0.9855, 0.9872, 0.9887, 0.9901, 0.9912, 0.9923,\n 0.9932, 0.994, 0.9947, 0.9953, 0.9959, 0.9964, 0.9968, 0.9972, 0.9975, 0.9978, 0.9981, 0.9983,\n 0.9985, 0.9987, 1,\n ],\n ms: 1008,\n};\n\nconst sample = ({ curve, ms }: Spring, t: number) => {\n const at = (t * ms) / 16;\n const i = Math.min(Math.floor(at), curve.length - 2);\n return curve[i]! + (curve[i + 1]! - curve[i]!) * (at - i);\n};\n\n/**\n * Runs `onFrame` with spring progress (0 to 1, may overshoot a little) every\n * frame until it settles (after `delay` ms), then `onDone`. Returns a cancel function. Under\n * reduced motion it jumps: one frame at 1, then done.\n */\nexport function tween(\n spring: Spring,\n onFrame: (progress: number) => void,\n onDone?: () => void,\n delay = 0,\n) {\n if (prefersReducedMotion()) {\n onFrame(1);\n onDone?.();\n return () => {};\n }\n const start = performance.now() + delay;\n let frame = 0;\n const tick = (now: number) => {\n // A frame's timestamp can precede `start`; clamp so the curve is never indexed below 0.\n const t = Math.min(Math.max((now - start) / spring.ms, 0), 1);\n onFrame(t < 1 ? sample(spring, t) : 1);\n if (t < 1) frame = requestAnimationFrame(tick);\n else onDone?.();\n };\n frame = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(frame);\n}\n\n/** A value driven by `retarget`. Read `.value` when painting; it is never React state. */\nexport interface Live {\n value: number;\n stop: () => void;\n}\n\nexport const live = (value: number): Live => ({ value, stop: () => {} });\n\n/**\n * Moves `target` to `to` on `spring`, starting from wherever it is now, so a\n * change mid-flight never jumps. `onFrame` repaints; `done` runs when it settles.\n */\nexport function retarget(\n target: Live,\n to: number,\n spring: Spring,\n onFrame: () => void,\n delay = 0,\n done?: () => void,\n) {\n target.stop();\n const from = target.value;\n if (from === to) {\n done?.();\n return;\n }\n target.stop = tween(\n spring,\n (p) => {\n target.value = from + (to - from) * p;\n onFrame();\n },\n done,\n delay,\n );\n}\n"],"mappings":"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"}
|