@refraction-ui/astro 0.4.2 → 0.4.5
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/dist/accordion/index.ts +1 -0
- package/dist/ai/ai-manager.ts +121 -0
- package/dist/ai/index.ts +23 -0
- package/dist/ai/mock-providers.ts +68 -0
- package/dist/ai/sanitizer.ts +174 -0
- package/dist/ai/tts-manager.ts +80 -0
- package/dist/ai/types.ts +72 -0
- package/dist/animated-text/animated-text.styles.ts +28 -0
- package/dist/animated-text/animated-text.ts +83 -0
- package/dist/animated-text/index.ts +15 -0
- package/dist/app-shell/app-shell.ts +252 -0
- package/dist/app-shell/auth-shell.ts +84 -0
- package/dist/app-shell/index.ts +22 -0
- package/dist/app-shell/page-shell.ts +125 -0
- package/dist/astro-accordion/Button.astro +1 -1
- package/dist/astro-ai/index.ts +1 -1
- package/dist/astro-animated-text/AnimatedText.astro +1 -1
- package/dist/astro-animated-text/TypewriterText.astro +1 -1
- package/dist/astro-app-shell/AppShell.astro +1 -1
- package/dist/astro-app-shell/AuthShell.astro +1 -1
- package/dist/astro-app-shell/AuthShellCard.astro +1 -1
- package/dist/astro-app-shell/PageShell.astro +1 -1
- package/dist/astro-app-shell/PageShellSection.astro +1 -1
- package/dist/astro-app-shell/index.ts +1 -1
- package/dist/astro-auth/index.ts +2 -2
- package/dist/astro-avatar/Avatar.astro +1 -1
- package/dist/astro-avatar/AvatarFallback.astro +1 -1
- package/dist/astro-avatar/AvatarImage.astro +1 -1
- package/dist/astro-avatar-group/AvatarGroup.astro +1 -1
- package/dist/astro-badge/Badge.astro +1 -1
- package/dist/astro-bottom-nav/BottomNav.astro +1 -1
- package/dist/astro-breadcrumbs/Breadcrumbs.astro +1 -1
- package/dist/astro-button/Button.astro +1 -1
- package/dist/astro-calendar/Calendar.astro +1 -1
- package/dist/astro-card/Card.astro +1 -1
- package/dist/astro-card/CardContent.astro +1 -1
- package/dist/astro-card/CardDescription.astro +1 -1
- package/dist/astro-card/CardFooter.astro +1 -1
- package/dist/astro-card/CardHeader.astro +1 -1
- package/dist/astro-card/CardTitle.astro +1 -1
- package/dist/astro-card-grid/CardGrid.astro +1 -1
- package/dist/astro-carousel/Button.astro +1 -1
- package/dist/astro-charts/Bars.astro +1 -1
- package/dist/astro-charts/Chart.astro +1 -1
- package/dist/astro-charts/Circles.astro +1 -1
- package/dist/astro-charts/Histogram.astro +1 -1
- package/dist/astro-charts/Line.astro +1 -1
- package/dist/astro-charts/PieChart.astro +1 -1
- package/dist/astro-charts/ScatterPlot.astro +1 -1
- package/dist/astro-charts/index.ts +1 -1
- package/dist/astro-checkbox/Checkbox.astro +1 -1
- package/dist/astro-code-editor/CodeEditor.astro +1 -1
- package/dist/astro-collapsible/Collapsible.astro +1 -1
- package/dist/astro-collapsible/CollapsibleContent.astro +1 -1
- package/dist/astro-command/Command.astro +1 -1
- package/dist/astro-command/CommandGroup.astro +1 -1
- package/dist/astro-command/CommandInput.astro +1 -1
- package/dist/astro-command/CommandItem.astro +1 -1
- package/dist/astro-command-input/CommandInput.astro +1 -1
- package/dist/astro-content-protection/ContentProtection.astro +1 -1
- package/dist/astro-data-table/DataTable.astro +1 -1
- package/dist/astro-date-picker/DatePicker.astro +1 -1
- package/dist/astro-device-frame/DeviceFrame.astro +1 -1
- package/dist/astro-dialog/DialogContent.astro +1 -1
- package/dist/astro-dialog/DialogOverlay.astro +1 -1
- package/dist/astro-diff-viewer/DiffViewer.astro +1 -1
- package/dist/astro-dropdown-menu/DropdownMenuContent.astro +1 -1
- package/dist/astro-dropdown-menu/DropdownMenuItem.astro +1 -1
- package/dist/astro-emoji-picker/EmojiPicker.astro +1 -1
- package/dist/astro-feedback-dialog/FeedbackDialog.astro +1 -1
- package/dist/astro-file-upload/FileUpload.astro +1 -1
- package/dist/astro-footer/Footer.astro +1 -1
- package/dist/astro-inline-editor/InlineEditor.astro +1 -1
- package/dist/astro-input/Input.astro +1 -1
- package/dist/astro-input-group/InputGroup.astro +1 -1
- package/dist/astro-input-group/InputGroupAddon.astro +1 -1
- package/dist/astro-input-group/InputGroupButton.astro +1 -1
- package/dist/astro-install-prompt/InstallPrompt.astro +1 -1
- package/dist/astro-keyboard-shortcut/ShortcutBadge.astro +1 -1
- package/dist/astro-language-selector/LanguageSelector.astro +1 -1
- package/dist/astro-link-card/LinkCard.astro +1 -1
- package/dist/astro-markdown-renderer/MarkdownRenderer.astro +1 -1
- package/dist/astro-mobile-nav/MobileNav.astro +1 -1
- package/dist/astro-mobile-nav/MobileNavLink.astro +1 -1
- package/dist/astro-mobile-nav/MobileNavTrigger.astro +1 -1
- package/dist/astro-navbar/Navbar.astro +1 -1
- package/dist/astro-otp-input/OtpInput.astro +1 -1
- package/dist/astro-payment/Payment.astro +1 -1
- package/dist/astro-popover/PopoverContent.astro +1 -1
- package/dist/astro-presence-indicator/PresenceIndicator.astro +1 -1
- package/dist/astro-progress-display/BadgeDisplay.astro +1 -1
- package/dist/astro-progress-display/ProgressBar.astro +1 -1
- package/dist/astro-progress-display/StatsGrid.astro +1 -1
- package/dist/astro-radio/RadioGroup.astro +1 -1
- package/dist/astro-radio/RadioItem.astro +1 -1
- package/dist/astro-reaction-bar/ReactionBar.astro +1 -1
- package/dist/astro-resizable-layout/ResizableDivider.astro +1 -1
- package/dist/astro-resizable-layout/ResizableLayout.astro +1 -1
- package/dist/astro-resizable-layout/ResizablePane.astro +1 -1
- package/dist/astro-resizable-layout/index.ts +1 -1
- package/dist/astro-rich-editor/index.ts +2 -2
- package/dist/astro-search-bar/SearchBar.astro +1 -1
- package/dist/astro-search-bar/SearchResults.astro +1 -1
- package/dist/astro-select/Select.astro +1 -1
- package/dist/astro-select/SelectContent.astro +1 -1
- package/dist/astro-select/SelectItem.astro +1 -1
- package/dist/astro-select/SelectTrigger.astro +1 -1
- package/dist/astro-sidebar/Sidebar.astro +1 -1
- package/dist/astro-skeleton/Skeleton.astro +1 -1
- package/dist/astro-slide-viewer/SlideViewer.astro +1 -1
- package/dist/astro-status-indicator/StatusIndicator.astro +1 -1
- package/dist/astro-switch/Switch.astro +1 -1
- package/dist/astro-table-of-contents/TableOfContents.astro +1 -1
- package/dist/astro-tabs/Tabs.astro +1 -1
- package/dist/astro-tabs/TabsList.astro +1 -1
- package/dist/astro-tabs/TabsTrigger.astro +1 -1
- package/dist/astro-textarea/Textarea.astro +1 -1
- package/dist/astro-theme/ThemeScript.astro +1 -1
- package/dist/astro-thread-view/ThreadView.astro +1 -1
- package/dist/astro-toast/Toast.astro +1 -1
- package/dist/astro-tooltip/TooltipContent.astro +1 -1
- package/dist/astro-version-selector/VersionSelector.astro +1 -1
- package/dist/astro-video-player/VideoPlayer.astro +1 -1
- package/dist/auth/adapters/mock.ts +74 -0
- package/dist/auth/auth-machine.ts +120 -0
- package/dist/auth/index.ts +22 -0
- package/dist/auth/rbac.ts +44 -0
- package/dist/auth/types.ts +44 -0
- package/dist/avatar/avatar.styles.ts +47 -0
- package/dist/avatar/avatar.ts +73 -0
- package/dist/avatar/index.ts +15 -0
- package/dist/avatar-group/avatar-group.styles.ts +63 -0
- package/dist/avatar-group/avatar-group.ts +106 -0
- package/dist/avatar-group/index.ts +18 -0
- package/dist/badge/badge.styles.ts +24 -0
- package/dist/badge/badge.ts +48 -0
- package/dist/badge/index.ts +10 -0
- package/dist/bottom-nav/bottom-nav.styles.ts +18 -0
- package/dist/bottom-nav/bottom-nav.ts +33 -0
- package/dist/bottom-nav/index.ts +2 -0
- package/dist/breadcrumbs/breadcrumbs.styles.ts +20 -0
- package/dist/breadcrumbs/breadcrumbs.ts +72 -0
- package/dist/breadcrumbs/index.ts +8 -0
- package/dist/button/button.styles.ts +38 -0
- package/dist/button/button.ts +64 -0
- package/dist/button/index.ts +10 -0
- package/dist/calendar/calendar.styles.ts +21 -0
- package/dist/calendar/calendar.ts +202 -0
- package/dist/calendar/index.ts +9 -0
- package/dist/callout/callout.ts +1 -0
- package/dist/callout/index.ts +1 -0
- package/dist/card/card.styles.ts +55 -0
- package/dist/card/card.ts +45 -0
- package/dist/card/index.ts +20 -0
- package/dist/card-grid/card-grid.ts +9 -0
- package/dist/card-grid/index.ts +1 -0
- package/dist/carousel/index.ts +1 -0
- package/dist/charts/axis.ts +50 -0
- package/dist/charts/chart.ts +73 -0
- package/dist/charts/colorblind.ts +100 -0
- package/dist/charts/data.ts +72 -0
- package/dist/charts/geometry.ts +54 -0
- package/dist/charts/index.ts +61 -0
- package/dist/charts/scales.ts +151 -0
- package/dist/checkbox/checkbox.styles.ts +33 -0
- package/dist/checkbox/checkbox.ts +73 -0
- package/dist/checkbox/index.ts +10 -0
- package/dist/code-block/code-block.ts +1 -0
- package/dist/code-block/index.ts +1 -0
- package/dist/code-editor/code-editor.styles.ts +32 -0
- package/dist/code-editor/code-editor.ts +140 -0
- package/dist/code-editor/index.ts +9 -0
- package/dist/collapsible/collapsible.styles.ts +14 -0
- package/dist/collapsible/collapsible.ts +93 -0
- package/dist/collapsible/index.ts +7 -0
- package/dist/command/command.styles.ts +27 -0
- package/dist/command/command.ts +179 -0
- package/dist/command/index.ts +14 -0
- package/dist/command-input/command-input.ts +176 -0
- package/dist/command-input/index.ts +1 -0
- package/dist/content-protection/content-protection.styles.ts +9 -0
- package/dist/content-protection/content-protection.ts +68 -0
- package/dist/content-protection/index.ts +8 -0
- package/dist/data-table/data-table.styles.ts +55 -0
- package/dist/data-table/data-table.ts +183 -0
- package/dist/data-table/index.ts +15 -0
- package/dist/date-picker/date-picker.styles.ts +31 -0
- package/dist/date-picker/date-picker.ts +333 -0
- package/dist/date-picker/index.ts +18 -0
- package/dist/device-frame/device-frame.styles.ts +21 -0
- package/dist/device-frame/device-frame.ts +112 -0
- package/dist/device-frame/index.ts +10 -0
- package/dist/dialog/dialog.styles.ts +8 -0
- package/dist/dialog/dialog.ts +133 -0
- package/dist/dialog/index.ts +8 -0
- package/dist/diff-viewer/diff-viewer.styles.ts +124 -0
- package/dist/diff-viewer/diff-viewer.ts +255 -0
- package/dist/diff-viewer/index.ts +20 -0
- package/dist/dropdown-menu/dropdown-menu.styles.ts +9 -0
- package/dist/dropdown-menu/dropdown-menu.ts +139 -0
- package/dist/dropdown-menu/index.ts +9 -0
- package/dist/emoji-picker/emoji-data.ts +282 -0
- package/dist/emoji-picker/emoji-picker.styles.ts +32 -0
- package/dist/emoji-picker/emoji-picker.ts +165 -0
- package/dist/emoji-picker/index.ts +25 -0
- package/dist/feedback-dialog/feedback-dialog.styles.ts +15 -0
- package/dist/feedback-dialog/feedback-dialog.ts +147 -0
- package/dist/feedback-dialog/index.ts +11 -0
- package/dist/file-upload/file-upload.styles.ts +30 -0
- package/dist/file-upload/file-upload.ts +260 -0
- package/dist/file-upload/index.ts +19 -0
- package/dist/footer/footer.styles.ts +5 -0
- package/dist/footer/footer.ts +32 -0
- package/dist/footer/index.ts +9 -0
- package/dist/index.ts +1 -1
- package/dist/inline-editor/index.ts +13 -0
- package/dist/inline-editor/inline-editor.styles.ts +42 -0
- package/dist/inline-editor/inline-editor.ts +96 -0
- package/dist/input/index.ts +9 -0
- package/dist/input/input.styles.ts +15 -0
- package/dist/input/input.ts +84 -0
- package/dist/input-group/index.ts +13 -0
- package/dist/input-group/input-group.styles.ts +50 -0
- package/dist/input-group/input-group.ts +44 -0
- package/dist/install-prompt/index.ts +9 -0
- package/dist/install-prompt/install-prompt.styles.ts +5 -0
- package/dist/install-prompt/install-prompt.ts +68 -0
- package/dist/keyboard-shortcut/index.ts +12 -0
- package/dist/keyboard-shortcut/keyboard-shortcut.styles.ts +8 -0
- package/dist/keyboard-shortcut/keyboard-shortcut.ts +153 -0
- package/dist/language-selector/index.ts +8 -0
- package/dist/language-selector/language-selector.styles.ts +28 -0
- package/dist/language-selector/language-selector.ts +149 -0
- package/dist/link-card/index.ts +1 -0
- package/dist/link-card/link-card.ts +9 -0
- package/dist/markdown-renderer/index.ts +8 -0
- package/dist/markdown-renderer/markdown-renderer.styles.ts +31 -0
- package/dist/markdown-renderer/markdown-renderer.ts +251 -0
- package/dist/mobile-nav/index.ts +14 -0
- package/dist/mobile-nav/mobile-nav.styles.ts +39 -0
- package/dist/mobile-nav/mobile-nav.ts +74 -0
- package/dist/navbar/index.ts +9 -0
- package/dist/navbar/navbar.styles.ts +29 -0
- package/dist/navbar/navbar.ts +39 -0
- package/dist/otp-input/index.ts +13 -0
- package/dist/otp-input/otp-input.styles.ts +51 -0
- package/dist/otp-input/otp-input.ts +198 -0
- package/dist/pagination/index.ts +1 -0
- package/dist/payment/index.ts +1 -0
- package/dist/payment/payment.ts +16 -0
- package/dist/popover/index.ts +7 -0
- package/dist/popover/popover.styles.ts +16 -0
- package/dist/popover/popover.ts +89 -0
- package/dist/presence-indicator/index.ts +14 -0
- package/dist/presence-indicator/presence-indicator.styles.ts +29 -0
- package/dist/presence-indicator/presence-indicator.ts +63 -0
- package/dist/progress-display/index.ts +15 -0
- package/dist/progress-display/progress-display.styles.ts +72 -0
- package/dist/progress-display/progress-display.ts +60 -0
- package/dist/radio/index.ts +8 -0
- package/dist/radio/radio.styles.ts +34 -0
- package/dist/radio/radio.ts +70 -0
- package/dist/reaction-bar/index.ts +14 -0
- package/dist/reaction-bar/reaction-bar.styles.ts +23 -0
- package/dist/reaction-bar/reaction-bar.ts +87 -0
- package/dist/resizable-layout/index.ts +12 -0
- package/dist/resizable-layout/resizable-layout.styles.ts +50 -0
- package/dist/resizable-layout/resizable-layout.ts +139 -0
- package/dist/rich-editor/editor.ts +342 -0
- package/dist/rich-editor/emoji.ts +175 -0
- package/dist/rich-editor/history.ts +79 -0
- package/dist/rich-editor/index.ts +106 -0
- package/dist/rich-editor/markdown-shortcuts.ts +248 -0
- package/dist/rich-editor/mentions.ts +162 -0
- package/dist/rich-editor/model.ts +206 -0
- package/dist/rich-editor/operations.ts +772 -0
- package/dist/rich-editor/selection.ts +157 -0
- package/dist/rich-editor/serialization.ts +711 -0
- package/dist/rich-editor/slash-commands.ts +212 -0
- package/dist/search-bar/index.ts +7 -0
- package/dist/search-bar/search-bar.styles.ts +29 -0
- package/dist/search-bar/search-bar.ts +130 -0
- package/dist/select/index.ts +13 -0
- package/dist/select/select.styles.ts +52 -0
- package/dist/select/select.ts +152 -0
- package/dist/sidebar/index.ts +9 -0
- package/dist/sidebar/sidebar.styles.ts +27 -0
- package/dist/sidebar/sidebar.ts +59 -0
- package/dist/skeleton/index.ts +8 -0
- package/dist/skeleton/skeleton.styles.ts +16 -0
- package/dist/skeleton/skeleton.ts +39 -0
- package/dist/skip-to-content/index.ts +1 -0
- package/dist/skip-to-content/skip-to-content.ts +1 -0
- package/dist/slide-viewer/index.ts +16 -0
- package/dist/slide-viewer/slide-viewer.styles.ts +57 -0
- package/dist/slide-viewer/slide-viewer.ts +152 -0
- package/dist/slider/index.ts +1 -0
- package/dist/slider/slider.ts +2 -0
- package/dist/status-indicator/index.ts +15 -0
- package/dist/status-indicator/status-indicator.styles.ts +41 -0
- package/dist/status-indicator/status-indicator.ts +66 -0
- package/dist/steps/index.ts +1 -0
- package/dist/steps/steps.ts +1 -0
- package/dist/switch/index.ts +7 -0
- package/dist/switch/switch.styles.ts +55 -0
- package/dist/switch/switch.ts +63 -0
- package/dist/table-of-contents/index.ts +1 -0
- package/dist/table-of-contents/table-of-contents.ts +37 -0
- package/dist/tabs/index.ts +8 -0
- package/dist/tabs/tabs.styles.ts +9 -0
- package/dist/tabs/tabs.ts +133 -0
- package/dist/textarea/index.ts +7 -0
- package/dist/textarea/textarea.styles.ts +15 -0
- package/dist/textarea/textarea.ts +59 -0
- package/dist/theme/dom-adapters.ts +60 -0
- package/dist/theme/index.ts +18 -0
- package/dist/theme/theme-machine.ts +130 -0
- package/dist/theme/theme-script.ts +18 -0
- package/dist/thread-view/index.ts +26 -0
- package/dist/thread-view/thread-view.styles.ts +35 -0
- package/dist/thread-view/thread-view.ts +176 -0
- package/dist/toast/index.ts +13 -0
- package/dist/toast/toast.styles.ts +19 -0
- package/dist/toast/toast.ts +203 -0
- package/dist/tooltip/index.ts +7 -0
- package/dist/tooltip/tooltip.styles.ts +16 -0
- package/dist/tooltip/tooltip.ts +109 -0
- package/dist/version-selector/index.ts +12 -0
- package/dist/version-selector/version-selector.styles.ts +41 -0
- package/dist/version-selector/version-selector.ts +136 -0
- package/dist/video-player/index.ts +12 -0
- package/dist/video-player/video-player.styles.ts +42 -0
- package/dist/video-player/video-player.ts +100 -0
- package/package.json +1 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
import { generateId } from '../shared/index.ts'
|
|
3
|
+
|
|
4
|
+
export interface CalendarProps {
|
|
5
|
+
/** Selected date (controlled) */
|
|
6
|
+
value?: Date
|
|
7
|
+
/** Default selected date (uncontrolled) */
|
|
8
|
+
defaultValue?: Date
|
|
9
|
+
/** Callback when a date is selected */
|
|
10
|
+
onSelect?: (date: Date) => void
|
|
11
|
+
/** Displayed month (controlled) */
|
|
12
|
+
month?: Date
|
|
13
|
+
/** Callback when the displayed month changes */
|
|
14
|
+
onMonthChange?: (month: Date) => void
|
|
15
|
+
/** Minimum selectable date */
|
|
16
|
+
minDate?: Date
|
|
17
|
+
/** Maximum selectable date */
|
|
18
|
+
maxDate?: Date
|
|
19
|
+
/** Specific dates to disable */
|
|
20
|
+
disabledDates?: Date[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CalendarDay {
|
|
24
|
+
/** The date this cell represents */
|
|
25
|
+
date: Date
|
|
26
|
+
/** Whether the date is today */
|
|
27
|
+
isToday: boolean
|
|
28
|
+
/** Whether the date is currently selected */
|
|
29
|
+
isSelected: boolean
|
|
30
|
+
/** Whether the date belongs to the currently displayed month */
|
|
31
|
+
isCurrentMonth: boolean
|
|
32
|
+
/** Whether the date is disabled */
|
|
33
|
+
isDisabled: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CalendarState {
|
|
37
|
+
/** The currently displayed month */
|
|
38
|
+
currentMonth: Date
|
|
39
|
+
/** The currently selected date, if any */
|
|
40
|
+
selectedDate: Date | undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CalendarAPI {
|
|
44
|
+
/** Current calendar state */
|
|
45
|
+
state: CalendarState
|
|
46
|
+
/** Array of day objects for the current month grid (always 42: 6 weeks x 7 days) */
|
|
47
|
+
days: CalendarDay[]
|
|
48
|
+
/** Navigate to the previous month */
|
|
49
|
+
prevMonth(): void
|
|
50
|
+
/** Navigate to the next month */
|
|
51
|
+
nextMonth(): void
|
|
52
|
+
/** Select a date */
|
|
53
|
+
select(date: Date): void
|
|
54
|
+
/** ARIA attributes for the calendar grid */
|
|
55
|
+
ariaProps: Partial<AccessibilityProps> & Record<string, unknown>
|
|
56
|
+
/** Get ARIA attributes for a specific day cell */
|
|
57
|
+
getDayAriaProps(day: CalendarDay): Partial<AccessibilityProps> & Record<string, unknown>
|
|
58
|
+
/** Generated IDs */
|
|
59
|
+
ids: {
|
|
60
|
+
grid: string
|
|
61
|
+
label: string
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Check if two dates represent the same calendar day */
|
|
66
|
+
function isSameDay(a: Date, b: Date): boolean {
|
|
67
|
+
return (
|
|
68
|
+
a.getFullYear() === b.getFullYear() &&
|
|
69
|
+
a.getMonth() === b.getMonth() &&
|
|
70
|
+
a.getDate() === b.getDate()
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Get the first day of a month */
|
|
75
|
+
function startOfMonth(date: Date): Date {
|
|
76
|
+
return new Date(date.getFullYear(), date.getMonth(), 1)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Get day names */
|
|
80
|
+
const DAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
|
81
|
+
|
|
82
|
+
export function createCalendar(props: CalendarProps = {}): CalendarAPI {
|
|
83
|
+
const {
|
|
84
|
+
value: controlledValue,
|
|
85
|
+
defaultValue,
|
|
86
|
+
onSelect,
|
|
87
|
+
month: controlledMonth,
|
|
88
|
+
onMonthChange,
|
|
89
|
+
minDate,
|
|
90
|
+
maxDate,
|
|
91
|
+
disabledDates = [],
|
|
92
|
+
} = props
|
|
93
|
+
|
|
94
|
+
const today = new Date()
|
|
95
|
+
|
|
96
|
+
// Determine selected date
|
|
97
|
+
const selectedDate = controlledValue ?? defaultValue
|
|
98
|
+
|
|
99
|
+
// Determine current displayed month
|
|
100
|
+
const currentMonth = controlledMonth
|
|
101
|
+
? startOfMonth(controlledMonth)
|
|
102
|
+
: selectedDate
|
|
103
|
+
? startOfMonth(selectedDate)
|
|
104
|
+
: startOfMonth(today)
|
|
105
|
+
|
|
106
|
+
const gridId = generateId('rfr-calendar')
|
|
107
|
+
const labelId = generateId('rfr-calendar-label')
|
|
108
|
+
|
|
109
|
+
function isDateDisabled(date: Date): boolean {
|
|
110
|
+
if (minDate && date < startOfDay(minDate)) return true
|
|
111
|
+
if (maxDate && date > endOfDay(maxDate)) return true
|
|
112
|
+
return disabledDates.some((d) => isSameDay(d, date))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function startOfDay(date: Date): Date {
|
|
116
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function endOfDay(date: Date): Date {
|
|
120
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Build the 42-cell grid for the current month view */
|
|
124
|
+
function buildDays(): CalendarDay[] {
|
|
125
|
+
const year = currentMonth.getFullYear()
|
|
126
|
+
const month = currentMonth.getMonth()
|
|
127
|
+
|
|
128
|
+
// First day of the month
|
|
129
|
+
const first = new Date(year, month, 1)
|
|
130
|
+
// Day of week the month starts on (0 = Sunday)
|
|
131
|
+
const startDow = first.getDay()
|
|
132
|
+
|
|
133
|
+
// Start from the Sunday before (or on) the first
|
|
134
|
+
const gridStart = new Date(year, month, 1 - startDow)
|
|
135
|
+
|
|
136
|
+
const days: CalendarDay[] = []
|
|
137
|
+
for (let i = 0; i < 42; i++) {
|
|
138
|
+
const date = new Date(
|
|
139
|
+
gridStart.getFullYear(),
|
|
140
|
+
gridStart.getMonth(),
|
|
141
|
+
gridStart.getDate() + i,
|
|
142
|
+
)
|
|
143
|
+
days.push({
|
|
144
|
+
date,
|
|
145
|
+
isToday: isSameDay(date, today),
|
|
146
|
+
isSelected: selectedDate ? isSameDay(date, selectedDate) : false,
|
|
147
|
+
isCurrentMonth: date.getMonth() === month,
|
|
148
|
+
isDisabled: isDateDisabled(date),
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
return days
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function prevMonth(): void {
|
|
155
|
+
const prev = new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1)
|
|
156
|
+
onMonthChange?.(prev)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function nextMonth(): void {
|
|
160
|
+
const next = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1)
|
|
161
|
+
onMonthChange?.(next)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function select(date: Date): void {
|
|
165
|
+
if (!isDateDisabled(date)) {
|
|
166
|
+
onSelect?.(date)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const ariaProps: Partial<AccessibilityProps> & Record<string, unknown> = {
|
|
171
|
+
role: 'grid',
|
|
172
|
+
'aria-labelledby': labelId,
|
|
173
|
+
id: gridId,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getDayAriaProps(day: CalendarDay): Partial<AccessibilityProps> & Record<string, unknown> {
|
|
177
|
+
return {
|
|
178
|
+
role: 'gridcell',
|
|
179
|
+
'aria-selected': day.isSelected,
|
|
180
|
+
'aria-disabled': day.isDisabled,
|
|
181
|
+
'aria-current': day.isToday ? ('date' as const) : undefined,
|
|
182
|
+
'aria-label': `${DAY_NAMES[day.date.getDay()]}, ${day.date.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}`,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
state: {
|
|
188
|
+
currentMonth,
|
|
189
|
+
selectedDate,
|
|
190
|
+
},
|
|
191
|
+
days: buildDays(),
|
|
192
|
+
prevMonth,
|
|
193
|
+
nextMonth,
|
|
194
|
+
select,
|
|
195
|
+
ariaProps,
|
|
196
|
+
getDayAriaProps,
|
|
197
|
+
ids: {
|
|
198
|
+
grid: gridId,
|
|
199
|
+
label: labelId,
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const callout = {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './callout';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
import type { TokenContract } from '../shared/index.ts'
|
|
3
|
+
|
|
4
|
+
export const cardTokens: TokenContract = {
|
|
5
|
+
name: 'card',
|
|
6
|
+
tokens: {
|
|
7
|
+
bg: { variable: '--rfr-card-bg', fallback: 'hsl(var(--card))' },
|
|
8
|
+
fg: { variable: '--rfr-card-fg', fallback: 'hsl(var(--card-foreground))' },
|
|
9
|
+
border: { variable: '--rfr-card-border', fallback: 'hsl(var(--border))' },
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const cardVariants = cva({
|
|
14
|
+
base: 'rounded-lg border bg-card text-card-foreground shadow',
|
|
15
|
+
variants: {
|
|
16
|
+
padding: {
|
|
17
|
+
none: '',
|
|
18
|
+
default: 'p-6',
|
|
19
|
+
compact: 'p-4',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
padding: 'none',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export const cardHeaderVariants = cva({
|
|
28
|
+
base: 'flex flex-col space-y-1.5 p-6',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const cardTitleVariants = cva({
|
|
32
|
+
base: 'font-semibold leading-none tracking-tight',
|
|
33
|
+
variants: {
|
|
34
|
+
size: {
|
|
35
|
+
sm: 'text-lg',
|
|
36
|
+
default: 'text-2xl',
|
|
37
|
+
lg: 'text-3xl',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultVariants: {
|
|
41
|
+
size: 'default',
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export const cardDescriptionVariants = cva({
|
|
46
|
+
base: 'text-sm text-muted-foreground',
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export const cardContentVariants = cva({
|
|
50
|
+
base: 'p-6 pt-0',
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
export const cardFooterVariants = cva({
|
|
54
|
+
base: 'flex items-center p-6 pt-0',
|
|
55
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
export interface CardProps {
|
|
4
|
+
/** Optional accessible role override */
|
|
5
|
+
role?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CardAPI {
|
|
9
|
+
/** ARIA attributes to spread on the element */
|
|
10
|
+
ariaProps: Partial<AccessibilityProps>
|
|
11
|
+
/** Data attributes for CSS styling hooks */
|
|
12
|
+
dataAttributes: Record<string, string>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createCard(props: CardProps = {}): CardAPI {
|
|
16
|
+
const ariaProps: Partial<AccessibilityProps> = {}
|
|
17
|
+
if (props.role) {
|
|
18
|
+
ariaProps.role = props.role
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
ariaProps,
|
|
23
|
+
dataAttributes: { 'data-slot': 'card' },
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createCardHeader(): { dataAttributes: Record<string, string> } {
|
|
28
|
+
return { dataAttributes: { 'data-slot': 'card-header' } }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createCardTitle(): { dataAttributes: Record<string, string> } {
|
|
32
|
+
return { dataAttributes: { 'data-slot': 'card-title' } }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createCardDescription(): { dataAttributes: Record<string, string> } {
|
|
36
|
+
return { dataAttributes: { 'data-slot': 'card-description' } }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createCardContent(): { dataAttributes: Record<string, string> } {
|
|
40
|
+
return { dataAttributes: { 'data-slot': 'card-content' } }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createCardFooter(): { dataAttributes: Record<string, string> } {
|
|
44
|
+
return { dataAttributes: { 'data-slot': 'card-footer' } }
|
|
45
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createCard,
|
|
3
|
+
createCardHeader,
|
|
4
|
+
createCardTitle,
|
|
5
|
+
createCardDescription,
|
|
6
|
+
createCardContent,
|
|
7
|
+
createCardFooter,
|
|
8
|
+
type CardProps,
|
|
9
|
+
type CardAPI,
|
|
10
|
+
} from './card.js'
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
cardVariants,
|
|
14
|
+
cardHeaderVariants,
|
|
15
|
+
cardTitleVariants,
|
|
16
|
+
cardDescriptionVariants,
|
|
17
|
+
cardContentVariants,
|
|
18
|
+
cardFooterVariants,
|
|
19
|
+
cardTokens,
|
|
20
|
+
} from './card.styles.js'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './card-grid.js'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { LinearScale, BandScale, TimeScale } from './scales.js'
|
|
2
|
+
|
|
3
|
+
type AnyScale = LinearScale | BandScale | TimeScale
|
|
4
|
+
|
|
5
|
+
export function generateTicks(
|
|
6
|
+
scale: AnyScale,
|
|
7
|
+
count?: number,
|
|
8
|
+
): (number | string | Date)[] {
|
|
9
|
+
if (scale.type === 'band') {
|
|
10
|
+
return scale.domain
|
|
11
|
+
}
|
|
12
|
+
if (scale.type === 'time') {
|
|
13
|
+
return scale.ticks(count)
|
|
14
|
+
}
|
|
15
|
+
// linear
|
|
16
|
+
return scale.ticks(count)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function formatTick(
|
|
20
|
+
value: number | string | Date,
|
|
21
|
+
type: 'number' | 'date' | 'string',
|
|
22
|
+
): string {
|
|
23
|
+
if (type === 'string') {
|
|
24
|
+
return String(value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (type === 'date') {
|
|
28
|
+
if (value instanceof Date) {
|
|
29
|
+
return value.toISOString().split('T')[0]
|
|
30
|
+
}
|
|
31
|
+
return String(value)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// type === 'number'
|
|
35
|
+
const num = value as number
|
|
36
|
+
if (!Number.isFinite(num)) return String(num)
|
|
37
|
+
|
|
38
|
+
// Check if integer
|
|
39
|
+
if (Number.isInteger(num)) {
|
|
40
|
+
// Format with locale separators for large numbers
|
|
41
|
+
return num.toLocaleString('en-US', { maximumFractionDigits: 0 })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Decimal: show up to 2 decimal places, strip trailing zeros
|
|
45
|
+
const formatted = num.toLocaleString('en-US', {
|
|
46
|
+
minimumFractionDigits: 0,
|
|
47
|
+
maximumFractionDigits: 2,
|
|
48
|
+
})
|
|
49
|
+
return formatted
|
|
50
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export interface Margin {
|
|
2
|
+
top: number
|
|
3
|
+
right: number
|
|
4
|
+
bottom: number
|
|
5
|
+
left: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface Dimensions {
|
|
9
|
+
width: number
|
|
10
|
+
height: number
|
|
11
|
+
margin: Margin
|
|
12
|
+
boundedWidth: number
|
|
13
|
+
boundedHeight: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ChartConfig {
|
|
17
|
+
width?: number
|
|
18
|
+
height?: number
|
|
19
|
+
margin?: Partial<Margin>
|
|
20
|
+
data?: unknown[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ChartAPI {
|
|
24
|
+
dimensions: Dimensions
|
|
25
|
+
scales: Record<string, unknown>
|
|
26
|
+
updateDimensions: (partial: Partial<ChartConfig>) => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DEFAULT_MARGIN: Margin = {
|
|
30
|
+
top: 40,
|
|
31
|
+
right: 30,
|
|
32
|
+
bottom: 40,
|
|
33
|
+
left: 75,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DEFAULT_WIDTH = 600
|
|
37
|
+
const DEFAULT_HEIGHT = 400
|
|
38
|
+
|
|
39
|
+
export function combineDimensions(partial: Partial<ChartConfig>): Dimensions {
|
|
40
|
+
const width = partial.width ?? DEFAULT_WIDTH
|
|
41
|
+
const height = partial.height ?? DEFAULT_HEIGHT
|
|
42
|
+
const margin: Margin = {
|
|
43
|
+
...DEFAULT_MARGIN,
|
|
44
|
+
...(partial.margin ?? {}),
|
|
45
|
+
}
|
|
46
|
+
const boundedWidth = Math.max(0, width - margin.left - margin.right)
|
|
47
|
+
const boundedHeight = Math.max(0, height - margin.top - margin.bottom)
|
|
48
|
+
|
|
49
|
+
return { width, height, margin, boundedWidth, boundedHeight }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createChart(config: ChartConfig): ChartAPI {
|
|
53
|
+
let dimensions = combineDimensions(config)
|
|
54
|
+
const scales: Record<string, unknown> = {}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
get dimensions() {
|
|
58
|
+
return dimensions
|
|
59
|
+
},
|
|
60
|
+
scales,
|
|
61
|
+
updateDimensions(partial: Partial<ChartConfig>) {
|
|
62
|
+
const merged: Partial<ChartConfig> = {
|
|
63
|
+
width: partial.width ?? dimensions.width,
|
|
64
|
+
height: partial.height ?? dimensions.height,
|
|
65
|
+
margin: {
|
|
66
|
+
...dimensions.margin,
|
|
67
|
+
...(partial.margin ?? {}),
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
dimensions = combineDimensions(merged)
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colorblind-safe color palettes for charts.
|
|
3
|
+
* Based on Wong (2011) "Points of view: Color blindness" — Nature Methods.
|
|
4
|
+
* These 8 colors are distinguishable by people with all types of color vision deficiency.
|
|
5
|
+
*/
|
|
6
|
+
export const COLORBLIND_SAFE_PALETTE = [
|
|
7
|
+
'#0072B2', // blue
|
|
8
|
+
'#E69F00', // orange
|
|
9
|
+
'#009E73', // green (blue-tinted, safe for protanopia/deuteranopia)
|
|
10
|
+
'#F0E442', // yellow
|
|
11
|
+
'#56B4E9', // sky blue
|
|
12
|
+
'#D55E00', // vermillion (not red — distinguishable from green)
|
|
13
|
+
'#CC79A7', // pink
|
|
14
|
+
'#000000', // black
|
|
15
|
+
] as const
|
|
16
|
+
|
|
17
|
+
/** Convert hex to HSL string for CSS variables */
|
|
18
|
+
export function hexToHSL(hex: string): string {
|
|
19
|
+
// Remove # prefix
|
|
20
|
+
const h = hex.replace(/^#/, '')
|
|
21
|
+
|
|
22
|
+
const r = parseInt(h.substring(0, 2), 16) / 255
|
|
23
|
+
const g = parseInt(h.substring(2, 4), 16) / 255
|
|
24
|
+
const b = parseInt(h.substring(4, 6), 16) / 255
|
|
25
|
+
|
|
26
|
+
const max = Math.max(r, g, b)
|
|
27
|
+
const min = Math.min(r, g, b)
|
|
28
|
+
const l = (max + min) / 2
|
|
29
|
+
|
|
30
|
+
if (max === min) {
|
|
31
|
+
// Achromatic
|
|
32
|
+
return `0 0% ${Math.round(l * 100)}%`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const d = max - min
|
|
36
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
|
37
|
+
|
|
38
|
+
let hue: number
|
|
39
|
+
if (max === r) {
|
|
40
|
+
hue = ((g - b) / d + (g < b ? 6 : 0)) / 6
|
|
41
|
+
} else if (max === g) {
|
|
42
|
+
hue = ((b - r) / d + 2) / 6
|
|
43
|
+
} else {
|
|
44
|
+
hue = ((r - g) / d + 4) / 6
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return `${Math.round(hue * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Get a colorblind-safe color by index (wraps around) */
|
|
51
|
+
export function getChartColor(index: number): string {
|
|
52
|
+
return COLORBLIND_SAFE_PALETTE[index % COLORBLIND_SAFE_PALETTE.length]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Pattern fills for charts — when color alone isn't enough.
|
|
57
|
+
* Returns SVG pattern definitions that can be used as fill.
|
|
58
|
+
*/
|
|
59
|
+
export type PatternType = 'solid' | 'diagonal' | 'dots' | 'crosshatch' | 'horizontal' | 'vertical' | 'zigzag' | 'checker'
|
|
60
|
+
|
|
61
|
+
export function getPatternDef(type: PatternType, color: string, id: string): string {
|
|
62
|
+
const size = 8
|
|
63
|
+
|
|
64
|
+
switch (type) {
|
|
65
|
+
case 'solid':
|
|
66
|
+
return `<pattern id="${id}" width="${size}" height="${size}" patternUnits="userSpaceOnUse"><rect width="${size}" height="${size}" fill="${color}"/></pattern>`
|
|
67
|
+
|
|
68
|
+
case 'diagonal':
|
|
69
|
+
return `<pattern id="${id}" width="${size}" height="${size}" patternUnits="userSpaceOnUse"><rect width="${size}" height="${size}" fill="${color}" opacity="0.3"/><line x1="0" y1="${size}" x2="${size}" y2="0" stroke="${color}" stroke-width="1.5"/></pattern>`
|
|
70
|
+
|
|
71
|
+
case 'dots':
|
|
72
|
+
return `<pattern id="${id}" width="${size}" height="${size}" patternUnits="userSpaceOnUse"><rect width="${size}" height="${size}" fill="${color}" opacity="0.3"/><circle cx="${size / 2}" cy="${size / 2}" r="1.5" fill="${color}"/></pattern>`
|
|
73
|
+
|
|
74
|
+
case 'crosshatch':
|
|
75
|
+
return `<pattern id="${id}" width="${size}" height="${size}" patternUnits="userSpaceOnUse"><rect width="${size}" height="${size}" fill="${color}" opacity="0.3"/><line x1="0" y1="0" x2="${size}" y2="${size}" stroke="${color}" stroke-width="1"/><line x1="${size}" y1="0" x2="0" y2="${size}" stroke="${color}" stroke-width="1"/></pattern>`
|
|
76
|
+
|
|
77
|
+
case 'horizontal':
|
|
78
|
+
return `<pattern id="${id}" width="${size}" height="${size}" patternUnits="userSpaceOnUse"><rect width="${size}" height="${size}" fill="${color}" opacity="0.3"/><line x1="0" y1="${size / 2}" x2="${size}" y2="${size / 2}" stroke="${color}" stroke-width="1.5"/></pattern>`
|
|
79
|
+
|
|
80
|
+
case 'vertical':
|
|
81
|
+
return `<pattern id="${id}" width="${size}" height="${size}" patternUnits="userSpaceOnUse"><rect width="${size}" height="${size}" fill="${color}" opacity="0.3"/><line x1="${size / 2}" y1="0" x2="${size / 2}" y2="${size}" stroke="${color}" stroke-width="1.5"/></pattern>`
|
|
82
|
+
|
|
83
|
+
case 'zigzag':
|
|
84
|
+
return `<pattern id="${id}" width="${size}" height="${size}" patternUnits="userSpaceOnUse"><rect width="${size}" height="${size}" fill="${color}" opacity="0.3"/><polyline points="0,${size} ${size / 2},0 ${size},${size}" fill="none" stroke="${color}" stroke-width="1.5"/></pattern>`
|
|
85
|
+
|
|
86
|
+
case 'checker':
|
|
87
|
+
return `<pattern id="${id}" width="${size}" height="${size}" patternUnits="userSpaceOnUse"><rect width="${size}" height="${size}" fill="${color}" opacity="0.3"/><rect width="${size / 2}" height="${size / 2}" fill="${color}"/><rect x="${size / 2}" y="${size / 2}" width="${size / 2}" height="${size / 2}" fill="${color}"/></pattern>`
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const PATTERNS: PatternType[] = [
|
|
92
|
+
'solid',
|
|
93
|
+
'diagonal',
|
|
94
|
+
'dots',
|
|
95
|
+
'crosshatch',
|
|
96
|
+
'horizontal',
|
|
97
|
+
'vertical',
|
|
98
|
+
'zigzag',
|
|
99
|
+
'checker',
|
|
100
|
+
]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export interface HistogramBin {
|
|
2
|
+
x0: number
|
|
3
|
+
x1: number
|
|
4
|
+
count: number
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function computeExtent(data: number[]): [number, number] {
|
|
8
|
+
let min = data[0]
|
|
9
|
+
let max = data[0]
|
|
10
|
+
for (let i = 1; i < data.length; i++) {
|
|
11
|
+
if (data[i] < min) min = data[i]
|
|
12
|
+
if (data[i] > max) max = data[i]
|
|
13
|
+
}
|
|
14
|
+
return [min, max]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function computeMean(data: number[]): number {
|
|
18
|
+
let sum = 0
|
|
19
|
+
for (let i = 0; i < data.length; i++) {
|
|
20
|
+
sum += data[i]
|
|
21
|
+
}
|
|
22
|
+
return sum / data.length
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function computeHistogramBins(
|
|
26
|
+
data: number[],
|
|
27
|
+
binCount?: number,
|
|
28
|
+
): HistogramBin[] {
|
|
29
|
+
const count = binCount ?? Math.ceil(Math.sqrt(data.length))
|
|
30
|
+
const [min, max] = computeExtent(data)
|
|
31
|
+
const span = max - min
|
|
32
|
+
|
|
33
|
+
// Handle case where all values are the same
|
|
34
|
+
if (span === 0) {
|
|
35
|
+
const bins: HistogramBin[] = []
|
|
36
|
+
for (let i = 0; i < count; i++) {
|
|
37
|
+
bins.push({ x0: min, x1: max, count: 0 })
|
|
38
|
+
}
|
|
39
|
+
// Put all items in the first bin
|
|
40
|
+
bins[0].count = data.length
|
|
41
|
+
return bins
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const binWidth = span / count
|
|
45
|
+
const bins: HistogramBin[] = []
|
|
46
|
+
for (let i = 0; i < count; i++) {
|
|
47
|
+
bins.push({
|
|
48
|
+
x0: min + i * binWidth,
|
|
49
|
+
x1: min + (i + 1) * binWidth,
|
|
50
|
+
count: 0,
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const value of data) {
|
|
55
|
+
let idx = Math.floor((value - min) / binWidth)
|
|
56
|
+
// Values at the max boundary go into the last bin
|
|
57
|
+
if (idx >= count) idx = count - 1
|
|
58
|
+
bins[idx].count++
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return bins
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function normalizeData<T>(
|
|
65
|
+
data: T[],
|
|
66
|
+
accessor: string | ((d: T) => unknown),
|
|
67
|
+
): unknown[] {
|
|
68
|
+
if (typeof accessor === 'string') {
|
|
69
|
+
return data.map((d) => (d as Record<string, unknown>)[accessor])
|
|
70
|
+
}
|
|
71
|
+
return data.map(accessor)
|
|
72
|
+
}
|