@vllnt/ui 0.3.0-canary.a42d8f4 → 0.3.0-canary.f3cafe8

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/components/activity-log/activity-log.js +5 -5
  3. package/dist/components/agent-activity/agent-activity.js +11 -5
  4. package/dist/components/ai-artifact/ai-artifact.js +23 -6
  5. package/dist/components/ai-chat-input/ai-chat-input.js +3 -1
  6. package/dist/components/ai-message-bubble/ai-message-bubble.js +1 -1
  7. package/dist/components/ai-sidebar/ai-sidebar.js +1 -0
  8. package/dist/components/auto-reload/auto-reload.js +28 -4
  9. package/dist/components/blog-card/blog-card.js +1 -1
  10. package/dist/components/candlestick-chart/candlestick-chart.js +7 -7
  11. package/dist/components/canvas-shell/canvas-foundation-demo.js +8 -8
  12. package/dist/components/canvas-view/canvas-view.js +71 -28
  13. package/dist/components/choropleth-map/choropleth-map.js +6 -1
  14. package/dist/components/chronological-timeline/chronological-timeline.js +1 -1
  15. package/dist/components/civilization-card/civilization-card.js +18 -1
  16. package/dist/components/combobox/combobox.js +2 -2
  17. package/dist/components/countdown-timer/countdown-timer.js +1 -1
  18. package/dist/components/data-table/data-table.js +4 -4
  19. package/dist/components/date-picker/date-picker.js +2 -2
  20. package/dist/components/era-comparison/era-comparison.js +40 -22
  21. package/dist/components/flashcard/flashcard.js +8 -2
  22. package/dist/components/geography-quiz-map/geography-quiz-map.js +6 -5
  23. package/dist/components/historic-timeline/historic-timeline.js +2 -2
  24. package/dist/components/interactive-timeline/interactive-timeline.js +15 -2
  25. package/dist/components/kbd/kbd.js +17 -9
  26. package/dist/components/knowledge-check/knowledge-check.js +42 -17
  27. package/dist/components/live-feed/live-feed.js +1 -1
  28. package/dist/components/map-timeline/map-timeline.js +1 -1
  29. package/dist/components/metric-gauge/metric-gauge.js +1 -1
  30. package/dist/components/model-comparison/model-comparison.js +25 -6
  31. package/dist/components/model-selector/model-selector.js +5 -5
  32. package/dist/components/multi-select/multi-select.js +3 -3
  33. package/dist/components/navbar-saas/navbar-saas.js +2 -2
  34. package/dist/components/newsletter-signup/newsletter-signup.js +2 -2
  35. package/dist/components/object-card/object-card.js +1 -1
  36. package/dist/components/parallel-timeline/parallel-timeline.js +2 -2
  37. package/dist/components/pricing-table/pricing-table.js +55 -18
  38. package/dist/components/progress-card/progress-card.js +1 -1
  39. package/dist/components/progress-tracker/progress-tracker.js +3 -3
  40. package/dist/components/prompt-templates/prompt-templates.js +15 -23
  41. package/dist/components/scope-selector/scope-selector.js +5 -5
  42. package/dist/components/search-dialog/search-dialog.js +1 -1
  43. package/dist/components/sparkline-grid/sparkline-grid.js +3 -2
  44. package/dist/components/static-code/static-code-copy.js +5 -10
  45. package/dist/components/status-board/status-board.js +1 -1
  46. package/dist/components/tour/tour.js +8 -2
  47. package/dist/components/transaction-list/transaction-list.js +13 -7
  48. package/dist/components/tutorial-card/tutorial-card.js +1 -1
  49. package/dist/components/tutorial-complete/tutorial-complete.js +2 -2
  50. package/dist/components/tutorial-mdx/tutorial-mdx.js +14 -14
  51. package/dist/components/usage-breakdown/usage-breakdown.js +1 -1
  52. package/dist/index.d.ts +75 -18
  53. package/dist/index.js +6 -0
  54. package/dist/lib/use-theme-preset.js +1 -0
  55. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -30,6 +30,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
30
30
  - **Time navigation** — `TimelineScrubber`, `PlaybackGhost`, `BottomActivityStrip`, `RunTimeline`.
31
31
  - Total component count: **225** (up from 140).
32
32
 
33
+ - **OKLCH theming system** — color tokens migrated to the OKLCH color space, with 13 runtime theme presets (`default`, `matrix`, `dracula`, `synthwave`, `tron`, `cyberpunk`, `nord`, `claude`, `chatgpt`, `gemini`, `dusk`, `cyberlime`, `aura`). New public theme exports from `@vllnt/ui`: `THEME_PRESETS`, `DEFAULT_THEME_PRESET`, `CUSTOM_THEME_NAME`, `isThemePresetName`, the `ThemePreset` and `ThemePresetName` types, plus the `useThemePreset` hook (with `UseThemePresetResult`), `setThemePreset`, and `setCustomTheme` for runtime preset switching and user-supplied custom themes.
34
+
33
35
  - **A11y heading-level override** — every title-bearing component (`ProfileSection`, `FAQ`, `Slideshow`, `WorldClockBar`, `TableOfContentsPanel`, `TableOfContents`, `KeyboardShortcutsHelp`, `Watchlist`, `OrderBook`, `HorizontalScrollRow`, `MarketTreemap`, `ActivityHeatmap`, `Glossary`, `StatusBoard`, `CodePlayground`, `Comparison`, `Quiz`, `Exercise`, `ShareSection`, `CompletionDialog`, `Checklist`, `LearningObjectives`, `CandlestickChart`, `StepByStep`) accepts an `as` prop (`"h1"`–`"h6"`); multi-title components (`ContentIntro`, `TutorialComplete`) expose `titleAs` + `tocLabelAs` / `sectionLabelAs`. Defaults preserve existing heading tags. `HeadingTag` is re-exported from `@vllnt/ui`.
34
36
 
35
37
  ### Changed
@@ -4,17 +4,17 @@ import { forwardRef, useMemo, useState } from "react";
4
4
  import { ArrowRight, ChevronLeft, ChevronRight } from "lucide-react";
5
5
  import { cn } from "../../lib/utils";
6
6
  import { Avatar, AvatarFallback } from "../avatar/avatar";
7
- import { Badge } from "../badge";
8
- import { Button } from "../button";
7
+ import { Badge } from "../badge/badge";
8
+ import { Button } from "../button/button";
9
9
  import {
10
10
  Card,
11
11
  CardContent,
12
12
  CardDescription,
13
13
  CardHeader,
14
14
  CardTitle
15
- } from "../card";
16
- import { ScrollArea } from "../scroll-area";
17
- import { Separator } from "../separator";
15
+ } from "../card/card";
16
+ import { ScrollArea } from "../scroll-area/scroll-area";
17
+ import { Separator } from "../separator/separator";
18
18
  const toneConfig = {
19
19
  danger: {
20
20
  badgeClassName: "border-destructive/20 bg-destructive/10 text-destructive dark:text-destructive",
@@ -59,6 +59,7 @@ const DEFAULT_LABELS = {
59
59
  elapsed: "Elapsed",
60
60
  expand: "Show details"
61
61
  };
62
+ const AgentActivityLabelsContext = createContext(DEFAULT_LABELS);
62
63
  const ACTIVITY_LIVE_REGION_ROLE = {
63
64
  completed: "status",
64
65
  error: "status",
@@ -75,8 +76,11 @@ const AgentActivity = forwardRef(
75
76
  status = "idle",
76
77
  ...rest
77
78
  } = props;
78
- const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
79
- return /* @__PURE__ */ jsxs(
79
+ const resolvedLabels = useMemo(
80
+ () => ({ ...DEFAULT_LABELS, ...labels }),
81
+ [labels]
82
+ );
83
+ return /* @__PURE__ */ jsx(AgentActivityLabelsContext.Provider, { value: resolvedLabels, children: /* @__PURE__ */ jsxs(
80
84
  "section",
81
85
  {
82
86
  "aria-live": status === "running" ? "polite" : "off",
@@ -103,7 +107,7 @@ const AgentActivity = forwardRef(
103
107
  /* @__PURE__ */ jsx("ol", { className: "flex flex-col gap-2", children })
104
108
  ]
105
109
  }
106
- );
110
+ ) });
107
111
  }
108
112
  );
109
113
  AgentActivity.displayName = "AgentActivity";
@@ -193,6 +197,7 @@ const AgentStep = forwardRef(
193
197
  const hasDetails = split.body.length > 0;
194
198
  const [open, setOpen] = useState(defaultOpen);
195
199
  const detailsId = useId();
200
+ const labels = useContext(AgentActivityLabelsContext);
196
201
  const handleToggle = useCallback(() => {
197
202
  setOpen((value) => !value);
198
203
  }, []);
@@ -222,15 +227,16 @@ const AgentStep = forwardRef(
222
227
  header: split.header,
223
228
  icon: resolvedIcon,
224
229
  iconClass: palette.iconClass,
225
- labels: DEFAULT_LABELS,
230
+ labels,
226
231
  onToggle: handleToggle,
227
232
  open
228
233
  }
229
234
  ),
230
- hasDetails && open ? /* @__PURE__ */ jsx(
235
+ hasDetails ? /* @__PURE__ */ jsx(
231
236
  "div",
232
237
  {
233
238
  className: "border-t border-border/60 px-3 py-2 text-xs",
239
+ hidden: !open,
234
240
  id: detailsId,
235
241
  children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 pl-8", children: split.body })
236
242
  }
@@ -5,7 +5,9 @@ import {
5
5
  forwardRef,
6
6
  useCallback,
7
7
  useContext,
8
+ useEffect,
8
9
  useMemo,
10
+ useRef,
9
11
  useState
10
12
  } from "react";
11
13
  import {
@@ -121,6 +123,24 @@ function ArtifactHeader({
121
123
  subtitle ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: subtitle }) : null
122
124
  ] });
123
125
  }
126
+ function useCopiedFlag() {
127
+ const [copied, setCopied] = useState(false);
128
+ const timerRef = useRef(void 0);
129
+ useEffect(
130
+ () => () => {
131
+ clearTimeout(timerRef.current);
132
+ },
133
+ []
134
+ );
135
+ const flagCopied = useCallback(() => {
136
+ setCopied(true);
137
+ clearTimeout(timerRef.current);
138
+ timerRef.current = setTimeout(() => {
139
+ setCopied(false);
140
+ }, COPIED_TIMEOUT_MS);
141
+ }, []);
142
+ return { copied, flagCopied };
143
+ }
124
144
  function useArtifactController(options) {
125
145
  const {
126
146
  defaultFullscreen,
@@ -133,7 +153,7 @@ function useArtifactController(options) {
133
153
  value
134
154
  } = options;
135
155
  const [fullscreen, setFullscreen] = useState(defaultFullscreen);
136
- const [copied, setCopied] = useState(false);
156
+ const { copied, flagCopied } = useCopiedFlag();
137
157
  const resolvedFilename = useMemo(
138
158
  () => buildFilename({ filename, language, title, type }),
139
159
  [filename, language, title, type]
@@ -141,12 +161,9 @@ function useArtifactController(options) {
141
161
  const copy = useCallback(async () => {
142
162
  const ok = await writeToClipboard(value);
143
163
  if (!ok) return false;
144
- setCopied(true);
145
- setTimeout(() => {
146
- setCopied(false);
147
- }, COPIED_TIMEOUT_MS);
164
+ flagCopied();
148
165
  return true;
149
- }, [value]);
166
+ }, [flagCopied, value]);
150
167
  const download = useCallback(() => {
151
168
  downloadValueAsFile(value, resolvedFilename);
152
169
  }, [resolvedFilename, value]);
@@ -4,7 +4,7 @@ import { cva } from "class-variance-authority";
4
4
  import { LoaderCircle, SendHorizontal } from "lucide-react";
5
5
  import { cn } from "../../lib/utils";
6
6
  import { Button } from "../button/button";
7
- import { Textarea } from "../textarea";
7
+ import { Textarea } from "../textarea/textarea";
8
8
  const formShellVariants = cva(
9
9
  "rounded-2xl border border-border/70 bg-background shadow-sm"
10
10
  );
@@ -48,6 +48,7 @@ const AIChatInput = forwardRef(
48
48
  className,
49
49
  disabled = false,
50
50
  helperText,
51
+ inputLabel = "Chat message",
51
52
  isSubmitting = false,
52
53
  onSubmit,
53
54
  onValueChange,
@@ -72,6 +73,7 @@ const AIChatInput = forwardRef(
72
73
  /* @__PURE__ */ jsx(
73
74
  Textarea,
74
75
  {
76
+ "aria-label": inputLabel,
75
77
  className: "min-h-[120px] resize-none rounded-xl border-0 bg-transparent p-1 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0",
76
78
  disabled,
77
79
  onChange: (event) => {
@@ -3,7 +3,7 @@ import { forwardRef } from "react";
3
3
  import { cva } from "class-variance-authority";
4
4
  import { cn } from "../../lib/utils";
5
5
  import { Avatar, AvatarFallback } from "../avatar/avatar";
6
- import { Badge } from "../badge";
6
+ import { Badge } from "../badge/badge";
7
7
  const bubbleVariants = cva(
8
8
  "rounded-2xl border px-4 py-3 shadow-sm transition-colors",
9
9
  {
@@ -126,6 +126,7 @@ const AISidebar = forwardRef(
126
126
  className
127
127
  ),
128
128
  "data-state": openState ? "open" : "closed",
129
+ inert: !openState,
129
130
  ref,
130
131
  style: { width: `${width.toString()}px` },
131
132
  ...rest,
@@ -180,13 +180,19 @@ function useAutoReloadController(options) {
180
180
  defaultReloadAmountCents,
181
181
  defaultThresholdCents,
182
182
  enabled: controlledEnabled,
183
+ onReloadAmountChange,
184
+ onThresholdChange,
183
185
  onToggle,
184
186
  reloadAmountCents: controlledReload,
185
187
  thresholdCents: controlledThreshold
186
188
  } = options;
187
189
  const [uncontrolledEnabled, setUncontrolledEnabled] = useState(defaultEnabled);
188
- const [threshold, setThreshold] = useState(defaultThresholdCents);
189
- const [reloadAmount, setReloadAmount] = useState(defaultReloadAmountCents);
190
+ const [uncontrolledThreshold, setUncontrolledThreshold] = useState(
191
+ defaultThresholdCents
192
+ );
193
+ const [uncontrolledReloadAmount, setUncontrolledReloadAmount] = useState(
194
+ defaultReloadAmountCents
195
+ );
190
196
  const enabled = controlledEnabled ?? uncontrolledEnabled;
191
197
  const handleToggle = useCallback(
192
198
  (next) => {
@@ -195,13 +201,27 @@ function useAutoReloadController(options) {
195
201
  },
196
202
  [controlledEnabled, onToggle]
197
203
  );
204
+ const setReloadAmount = useCallback(
205
+ (next) => {
206
+ if (controlledReload === void 0) setUncontrolledReloadAmount(next);
207
+ onReloadAmountChange?.(next);
208
+ },
209
+ [controlledReload, onReloadAmountChange]
210
+ );
211
+ const setThreshold = useCallback(
212
+ (next) => {
213
+ if (controlledThreshold === void 0) setUncontrolledThreshold(next);
214
+ onThresholdChange?.(next);
215
+ },
216
+ [controlledThreshold, onThresholdChange]
217
+ );
198
218
  return {
199
219
  enabled,
200
220
  handleToggle,
201
- reloadAmount: controlledReload ?? reloadAmount,
221
+ reloadAmount: controlledReload ?? uncontrolledReloadAmount,
202
222
  setReloadAmount,
203
223
  setThreshold,
204
- threshold: controlledThreshold ?? threshold
224
+ threshold: controlledThreshold ?? uncontrolledThreshold
205
225
  };
206
226
  }
207
227
  const DisabledBanner = forwardRef(
@@ -291,7 +311,9 @@ function useAutoReloadInternals(props) {
291
311
  enabled: controlledEnabled,
292
312
  labels,
293
313
  locale = DEFAULT_LOCALE,
314
+ onReloadAmountChange,
294
315
  onSave,
316
+ onThresholdChange,
295
317
  onToggle,
296
318
  reloadAmountCents: controlledReload,
297
319
  thresholdCents: controlledThreshold
@@ -308,6 +330,8 @@ function useAutoReloadInternals(props) {
308
330
  defaultReloadAmountCents,
309
331
  defaultThresholdCents,
310
332
  enabled: controlledEnabled,
333
+ onReloadAmountChange,
334
+ onThresholdChange,
311
335
  onToggle,
312
336
  reloadAmountCents: controlledReload,
313
337
  thresholdCents: controlledThreshold
@@ -7,7 +7,7 @@ import {
7
7
  CardDescription,
8
8
  CardHeader,
9
9
  CardTitle
10
- } from "../card";
10
+ } from "../card/card";
11
11
  function ContentCard({
12
12
  formatDate,
13
13
  href,
@@ -79,7 +79,7 @@ function PriceGrid({
79
79
  children: formatValue(tick.value)
80
80
  }
81
81
  )
82
- ] }, tick.value));
82
+ ] }, tick.y));
83
83
  }
84
84
  function CandleMarks({
85
85
  data,
@@ -95,12 +95,12 @@ function CandleMarks({
95
95
  const bodyY = Math.min(openY, closeY);
96
96
  const bodyHeight = Math.max(Math.abs(openY - closeY), 3);
97
97
  const isBullish = candle.close >= candle.open;
98
- const fill = isBullish ? "hsl(142 71% 45%)" : "hsl(348 83% 47%)";
99
- return /* @__PURE__ */ jsxs("g", { children: [
98
+ const colorClass = isBullish ? "text-emerald-600 dark:text-emerald-400" : "text-rose-600 dark:text-rose-400";
99
+ return /* @__PURE__ */ jsxs("g", { className: colorClass, children: [
100
100
  /* @__PURE__ */ jsx(
101
101
  "line",
102
102
  {
103
- stroke: fill,
103
+ stroke: "currentColor",
104
104
  strokeLinecap: "round",
105
105
  strokeWidth: 2,
106
106
  x1: centerX,
@@ -112,11 +112,11 @@ function CandleMarks({
112
112
  /* @__PURE__ */ jsx(
113
113
  "rect",
114
114
  {
115
- fill,
115
+ fill: "currentColor",
116
116
  fillOpacity: isBullish ? 0.25 : 0.18,
117
117
  height: bodyHeight,
118
118
  rx: 4,
119
- stroke: fill,
119
+ stroke: "currentColor",
120
120
  strokeWidth: 1.5,
121
121
  width: metrics.bodyWidth,
122
122
  x: centerX - metrics.bodyWidth / 2,
@@ -135,7 +135,7 @@ function CandleMarks({
135
135
  children: candle.label
136
136
  }
137
137
  )
138
- ] }, candle.label);
138
+ ] }, `${candle.label}-${index.toString()}`);
139
139
  });
140
140
  }
141
141
  function SessionPill({ sessionChange }) {
@@ -1,14 +1,14 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Activity, Bot, Compass, Layers3, Sparkles } from "lucide-react";
3
3
  import { BottomBar } from "../bottom-bar/bottom-bar";
4
- import { Button } from "../button";
5
- import { CanvasView } from "../canvas-view";
6
- import { ChatDockSection } from "../chat-dock-section";
7
- import { GlassPanel } from "../glass-panel";
8
- import { LeftRail } from "../left-rail";
9
- import { RightDock } from "../right-dock";
10
- import { TopBar } from "../top-bar";
11
- import { WorkspaceSwitcher } from "../workspace-switcher";
4
+ import { Button } from "../button/button";
5
+ import { CanvasView } from "../canvas-view/canvas-view";
6
+ import { ChatDockSection } from "../chat-dock-section/chat-dock-section";
7
+ import { GlassPanel } from "../glass-panel/glass-panel";
8
+ import { LeftRail } from "../left-rail/left-rail";
9
+ import { RightDock } from "../right-dock/right-dock";
10
+ import { TopBar } from "../top-bar/top-bar";
11
+ import { WorkspaceSwitcher } from "../workspace-switcher/workspace-switcher";
12
12
  import { CanvasShell } from "./canvas-shell";
13
13
  function DemoLeftBar() {
14
14
  return /* @__PURE__ */ jsx(GlassPanel, { className: "overflow-hidden", children: /* @__PURE__ */ jsxs(
@@ -46,11 +46,18 @@ function supportsScrollableOverflow(value) {
46
46
  return value === "auto" || value === "overlay" || value === "scroll";
47
47
  }
48
48
  function hasScrollableAxis(element, axis) {
49
- const style = window.getComputedStyle(element);
50
49
  if (axis === "x") {
51
- return supportsScrollableOverflow(style.overflowX) && element.scrollWidth > element.clientWidth;
50
+ if (element.scrollWidth <= element.clientWidth) {
51
+ return false;
52
+ }
53
+ return supportsScrollableOverflow(
54
+ window.getComputedStyle(element).overflowX
55
+ );
56
+ }
57
+ if (element.scrollHeight <= element.clientHeight) {
58
+ return false;
52
59
  }
53
- return supportsScrollableOverflow(style.overflowY) && element.scrollHeight > element.clientHeight;
60
+ return supportsScrollableOverflow(window.getComputedStyle(element).overflowY);
54
61
  }
55
62
  function hasScrollableAncestor(element, container, delta) {
56
63
  if (!container.contains(element) || element === container) {
@@ -67,8 +74,8 @@ function shouldHandleCanvasKeyboardEvent(event) {
67
74
  }
68
75
  return true;
69
76
  }
70
- function shouldHandleCanvasWheelEvent(event) {
71
- if (isHtmlElement(event.target) && hasScrollableAncestor(event.target, event.currentTarget, {
77
+ function shouldHandleCanvasWheelEvent(event, container) {
78
+ if (isHtmlElement(event.target) && hasScrollableAncestor(event.target, container, {
72
79
  x: event.deltaX,
73
80
  y: event.deltaY
74
81
  })) {
@@ -177,6 +184,7 @@ function useViewportState({
177
184
  };
178
185
  }
179
186
  function useCanvasKeyboardInteractions({
187
+ containerRef,
180
188
  nudgeViewport,
181
189
  resetViewport,
182
190
  setViewport,
@@ -184,24 +192,13 @@ function useCanvasKeyboardInteractions({
184
192
  zoomStep
185
193
  }) {
186
194
  const [isSpacePressed, setIsSpacePressed] = useState(false);
187
- const handleWheel = useCallback(
188
- (event) => {
189
- if (event.ctrlKey || event.metaKey) {
190
- event.preventDefault();
191
- setViewport({
192
- ...viewportRef.current,
193
- zoom: viewportRef.current.zoom + (event.deltaY > 0 ? -zoomStep : zoomStep)
194
- });
195
- return;
196
- }
197
- if (!shouldHandleCanvasWheelEvent(event)) {
198
- return;
199
- }
200
- event.preventDefault();
201
- nudgeViewport(-event.deltaX, -event.deltaY);
202
- },
203
- [nudgeViewport, setViewport, viewportRef, zoomStep]
204
- );
195
+ useCanvasWheel({
196
+ containerRef,
197
+ nudgeViewport,
198
+ setViewport,
199
+ viewportRef,
200
+ zoomStep
201
+ });
205
202
  const handleKeyDown = useCallback(
206
203
  (event) => {
207
204
  if (!shouldHandleCanvasKeyboardEvent(event)) {
@@ -233,7 +230,43 @@ function useCanvasKeyboardInteractions({
233
230
  },
234
231
  []
235
232
  );
236
- return { handleKeyDown, handleKeyUp, handleWheel, isSpacePressed };
233
+ const handleBlur = useCallback(() => {
234
+ setIsSpacePressed(false);
235
+ }, []);
236
+ return { handleBlur, handleKeyDown, handleKeyUp, isSpacePressed };
237
+ }
238
+ function useCanvasWheel({
239
+ containerRef,
240
+ nudgeViewport,
241
+ setViewport,
242
+ viewportRef,
243
+ zoomStep
244
+ }) {
245
+ useEffect(() => {
246
+ const container = containerRef.current;
247
+ if (!container) {
248
+ return;
249
+ }
250
+ const handleWheel = (event) => {
251
+ if (event.ctrlKey || event.metaKey) {
252
+ event.preventDefault();
253
+ setViewport({
254
+ ...viewportRef.current,
255
+ zoom: viewportRef.current.zoom + (event.deltaY > 0 ? -zoomStep : zoomStep)
256
+ });
257
+ return;
258
+ }
259
+ if (!shouldHandleCanvasWheelEvent(event, container)) {
260
+ return;
261
+ }
262
+ event.preventDefault();
263
+ nudgeViewport(-event.deltaX, -event.deltaY);
264
+ };
265
+ container.addEventListener("wheel", handleWheel, { passive: false });
266
+ return () => {
267
+ container.removeEventListener("wheel", handleWheel);
268
+ };
269
+ }, [containerRef, nudgeViewport, setViewport, viewportRef, zoomStep]);
237
270
  }
238
271
  function endCanvasDrag(event, dragOriginRef, setIsDragging) {
239
272
  dragOriginRef.current = null;
@@ -328,16 +361,17 @@ function useCanvasViewHandle(ref, viewportState) {
328
361
  }
329
362
  function CanvasInteractionLayer({
330
363
  children,
364
+ containerRef,
331
365
  instructionsId,
332
366
  isDragging,
333
367
  isSpacePressed,
368
+ onBlur,
334
369
  onKeyDown,
335
370
  onKeyUp,
336
371
  onPointerCancel,
337
372
  onPointerDown,
338
373
  onPointerMove,
339
374
  onPointerUp,
340
- onWheel,
341
375
  viewport
342
376
  }) {
343
377
  return /* @__PURE__ */ jsxs(
@@ -351,13 +385,14 @@ function CanvasInteractionLayer({
351
385
  isDragging || isSpacePressed ? "cursor-grab active:cursor-grabbing" : "cursor-default"
352
386
  ),
353
387
  "data-viewport": JSON.stringify(viewport),
388
+ onBlur,
354
389
  onKeyDown,
355
390
  onKeyUp,
356
391
  onPointerCancel,
357
392
  onPointerDown,
358
393
  onPointerMove,
359
394
  onPointerUp,
360
- onWheel,
395
+ ref: containerRef,
361
396
  role: "button",
362
397
  tabIndex: 0,
363
398
  children: [
@@ -369,6 +404,7 @@ function CanvasInteractionLayer({
369
404
  }
370
405
  function CanvasContentLayer({
371
406
  children,
407
+ isDragging,
372
408
  overlay,
373
409
  viewport
374
410
  }) {
@@ -376,7 +412,10 @@ function CanvasContentLayer({
376
412
  /* @__PURE__ */ jsx(
377
413
  "div",
378
414
  {
379
- className: "absolute inset-0 origin-top-left transition-transform duration-150 ease-out",
415
+ className: cn(
416
+ "absolute inset-0 origin-top-left",
417
+ isDragging ? "" : "transition-transform duration-150 ease-out"
418
+ ),
380
419
  style: {
381
420
  transform: `translate3d(${viewport.x}px, ${viewport.y}px, 0) scale(${viewport.zoom})`
382
421
  },
@@ -399,6 +438,7 @@ const CanvasView = forwardRef(
399
438
  ...props
400
439
  }, ref) => {
401
440
  const instructionsId = useId();
441
+ const interactionRef = useRef(null);
402
442
  const viewportState = useViewportState({
403
443
  defaultViewport,
404
444
  maxZoom,
@@ -406,6 +446,7 @@ const CanvasView = forwardRef(
406
446
  onViewportChange
407
447
  });
408
448
  const keyboard = useCanvasKeyboardInteractions({
449
+ containerRef: interactionRef,
409
450
  nudgeViewport: viewportState.nudgeViewport,
410
451
  resetViewport: viewportState.resetViewport,
411
452
  setViewport: viewportState.setViewport,
@@ -430,20 +471,22 @@ const CanvasView = forwardRef(
430
471
  children: /* @__PURE__ */ jsx(
431
472
  CanvasInteractionLayer,
432
473
  {
474
+ containerRef: interactionRef,
433
475
  instructionsId,
434
476
  isDragging: pointer.isDragging,
435
477
  isSpacePressed: keyboard.isSpacePressed,
478
+ onBlur: keyboard.handleBlur,
436
479
  onKeyDown: keyboard.handleKeyDown,
437
480
  onKeyUp: keyboard.handleKeyUp,
438
481
  onPointerCancel: pointer.handlePointerCancel,
439
482
  onPointerDown: pointer.handlePointerDown,
440
483
  onPointerMove: pointer.handlePointerMove,
441
484
  onPointerUp: pointer.handlePointerUp,
442
- onWheel: keyboard.handleWheel,
443
485
  viewport: viewportState.viewport,
444
486
  children: /* @__PURE__ */ jsx(
445
487
  CanvasContentLayer,
446
488
  {
489
+ isDragging: pointer.isDragging,
447
490
  overlay,
448
491
  viewport: viewportState.viewport,
449
492
  children
@@ -17,6 +17,11 @@ const DEFAULT_LABELS = {
17
17
  };
18
18
  const DEFAULT_SCALE = ["#f1f5f9", "#1d4ed8"];
19
19
  const DEFAULT_MISSING = "#e5e7eb";
20
+ function resolveMissingColor() {
21
+ if (typeof window === "undefined") return DEFAULT_MISSING;
22
+ const muted = getComputedStyle(document.documentElement).getPropertyValue("--muted").trim();
23
+ return muted ? `oklch(${muted})` : DEFAULT_MISSING;
24
+ }
20
25
  const ChoroplethContext = createContext(null);
21
26
  function useChoroplethContext() {
22
27
  const ctx = useContext(ChoroplethContext);
@@ -298,7 +303,7 @@ const ChoroplethMap = forwardRef(
298
303
  data,
299
304
  domain: domainProperty,
300
305
  labels,
301
- missingColor = DEFAULT_MISSING,
306
+ missingColor = resolveMissingColor(),
302
307
  onSelectRegion,
303
308
  regions,
304
309
  ...rest
@@ -280,7 +280,7 @@ function EventList({ activeId, children }) {
280
280
  return /* @__PURE__ */ jsx("ol", { className: "relative flex flex-col px-4 pb-6 md:px-6", children: children.map((child, index) => /* @__PURE__ */ jsx(
281
281
  "li",
282
282
  {
283
- className: "block list-none",
283
+ className: "group block list-none",
284
284
  "data-active": isReactElementWithEventId(child, activeId) ? "true" : void 0,
285
285
  "data-side": index % 2 === 0 ? "left" : "right",
286
286
  children: child
@@ -5,6 +5,8 @@ import {
5
5
  import { Globe } from "lucide-react";
6
6
  import { cn } from "../../lib/utils";
7
7
  import { Badge } from "../badge/badge";
8
+ const FALLBACK_ERA_MIN_YEAR = -3e3;
9
+ const FALLBACK_ERA_SPAN_YEARS = 5e3;
8
10
  const CIVILIZATION_COLOR_VARIANTS = {
9
11
  amber: {
10
12
  gradient: "from-amber-500/20 to-amber-700/40",
@@ -75,8 +77,17 @@ function CivilizationHero({ color, image, imageAlt }) {
75
77
  }
76
78
  );
77
79
  }
80
+ function getEraBarGeometry(era) {
81
+ const end = era.end ?? (/* @__PURE__ */ new Date()).getFullYear();
82
+ const min = Math.min(era.start, FALLBACK_ERA_MIN_YEAR);
83
+ const range = Math.max(end, min + FALLBACK_ERA_SPAN_YEARS) - min || 1;
84
+ const left = (era.start - min) / range * 100;
85
+ const width = Math.max((end - era.start) / range * 100, 1);
86
+ return { left, width };
87
+ }
78
88
  function EraTimeline({ era, label }) {
79
89
  const eraLabel = formatEra(era);
90
+ const { left, width } = getEraBarGeometry(era);
80
91
  return /* @__PURE__ */ jsxs(
81
92
  "div",
82
93
  {
@@ -85,7 +96,13 @@ function EraTimeline({ era, label }) {
85
96
  role: "img",
86
97
  children: [
87
98
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: eraLabel }),
88
- /* @__PURE__ */ jsx("div", { className: "h-1.5 w-full rounded-full bg-muted", children: /* @__PURE__ */ jsx("span", { className: "block h-full w-2/3 rounded-full bg-primary" }) })
99
+ /* @__PURE__ */ jsx("div", { className: "relative h-1.5 w-full rounded-full bg-muted", children: /* @__PURE__ */ jsx(
100
+ "span",
101
+ {
102
+ className: "absolute h-full rounded-full bg-primary",
103
+ style: { left: `${left.toString()}%`, width: `${width.toString()}%` }
104
+ }
105
+ ) })
89
106
  ]
90
107
  }
91
108
  );
@@ -11,8 +11,8 @@ import {
11
11
  CommandInput,
12
12
  CommandItem,
13
13
  CommandList
14
- } from "../command";
15
- import { Popover, PopoverContent, PopoverTrigger } from "../popover";
14
+ } from "../command/command";
15
+ import { Popover, PopoverContent, PopoverTrigger } from "../popover/popover";
16
16
  function useComboboxValue(value, onValueChange) {
17
17
  const [internalValue, setInternalValue] = React.useState(value ?? "");
18
18
  const resolvedValue = value ?? internalValue;
@@ -9,7 +9,7 @@ import {
9
9
  CardDescription,
10
10
  CardHeader,
11
11
  CardTitle
12
- } from "../card";
12
+ } from "../card/card";
13
13
  function normalizeDate(input) {
14
14
  if (input instanceof Date) {
15
15
  return new Date(input.getTime());