rift-ds 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/behaviors/LinkProvider.d.ts +3 -0
- package/behaviors/LocaleProvider.d.ts +27 -0
- package/behaviors/LocaleProvider.js +16 -0
- package/behaviors/direction.css +19 -0
- package/behaviors/direction.d.ts +40 -0
- package/behaviors/direction.js +56 -0
- package/behaviors/focusable.d.ts +2 -1
- package/behaviors/locale.d.ts +47 -0
- package/behaviors/locale.js +87 -0
- package/behaviors/shortcut-keys.d.ts +49 -0
- package/behaviors/shortcut-keys.js +129 -0
- package/behaviors/shortcuts.d.ts +75 -0
- package/behaviors/shortcuts.js +160 -0
- package/behaviors/useAnchoredPosition.d.ts +17 -4
- package/behaviors/useAnchoredPosition.js +8 -3
- package/behaviors/useFocusScope.d.ts +6 -0
- package/behaviors/useFocusScope.js +2 -0
- package/behaviors/useLayer.d.ts +9 -0
- package/behaviors/useLayer.js +4 -0
- package/behaviors/useScrollLock.d.ts +4 -0
- package/bin/rift-ds.mjs +3 -0
- package/chat/ChatWidget.css +24 -24
- package/chat/ChatWidget.js +4 -2
- package/chat/suggestions.d.ts +12 -0
- package/components/Accordion/Accordion.css +1 -1
- package/components/AgentPlan/AgentPlan.css +2 -2
- package/components/AgentRail/AgentRail.css +4 -4
- package/components/AgentRail/AgentRail.js +1 -1
- package/components/AgentStatus/AgentStatus.css +1 -1
- package/components/AiButton/AiButton.css +4 -4
- package/components/AiButton/AiButton.js +2 -1
- package/components/AlertDialog/AlertDialog.css +1 -1
- package/components/AnchorNav/AnchorNav.css +6 -6
- package/components/AnchorNav/AnchorNav.js +4 -2
- package/components/AnimatedNumber/AnimatedNumber.d.ts +6 -0
- package/components/AnimatedNumber/AnimatedNumber.js +12 -5
- package/components/AppLayout/AppLayout.css +3 -3
- package/components/AppSidebar/AppSidebar.css +20 -20
- package/components/AppSidebar/AppSidebar.js +6 -4
- package/components/ArtifactCard/ArtifactCard.css +1 -1
- package/components/ArtifactCard/ArtifactCard.js +2 -1
- package/components/ArtifactPanel/ArtifactPanel.js +2 -1
- package/components/Attachment/Attachment.css +4 -4
- package/components/Attachment/AttachmentDropZone.js +2 -1
- package/components/Attachment/AttachmentGroup.d.ts +2 -0
- package/components/Attachment/AttachmentGroup.js +4 -1
- package/components/Attachment/AttachmentViewer.d.ts +2 -0
- package/components/Attachment/AttachmentViewer.js +8 -13
- package/components/Attachment/fileTypes.d.ts +5 -3
- package/components/Attachment/fileTypes.js +8 -3
- package/components/Avatar/Avatar.css +1 -1
- package/components/AvatarGroup/AvatarGroup.css +1 -1
- package/components/BentoTile/BentoTile.css +15 -3
- package/components/BottomNav/BottomNav.css +6 -6
- package/components/BottomNav/BottomNav.js +3 -1
- package/components/Breadcrumb/Breadcrumb.js +2 -2
- package/components/Button/Button.css +1 -1
- package/components/Button/Button.js +4 -2
- package/components/Card/Card.css +3 -3
- package/components/CardStack/CardStack.js +4 -2
- package/components/Carousel/Carousel.css +5 -2
- package/components/Carousel/Carousel.js +11 -7
- package/components/Chart/AreaChart.d.ts +2 -0
- package/components/Chart/AreaChart.js +15 -6
- package/components/Chart/BarChart.d.ts +2 -0
- package/components/Chart/BarChart.js +15 -5
- package/components/Chart/Chart.css +6 -6
- package/components/Chart/ComboChart.d.ts +2 -0
- package/components/Chart/ComboChart.js +16 -6
- package/components/Chart/LineChart.d.ts +2 -0
- package/components/Chart/LineChart.js +15 -6
- package/components/Chart/PieChart.d.ts +2 -0
- package/components/Chart/PieChart.js +9 -5
- package/components/Chart/RadarChart.d.ts +2 -0
- package/components/Chart/RadarChart.js +15 -6
- package/components/Chart/RadialChart.d.ts +2 -0
- package/components/Chart/RadialChart.js +9 -5
- package/components/Chart/ScatterChart.d.ts +2 -0
- package/components/Chart/ScatterChart.js +17 -7
- package/components/Chart/StackedBarChart.d.ts +2 -0
- package/components/Chart/StackedBarChart.js +15 -7
- package/components/Chart/Treemap.d.ts +2 -0
- package/components/Chart/Treemap.js +9 -5
- package/components/ChatMarker/ChatMarker.js +2 -1
- package/components/ChatMessage/ChatMessage.css +2 -2
- package/components/ChatThread/ChatThread.css +1 -1
- package/components/Checkbox/Checkbox.css +4 -2
- package/components/CircularButton/CircularButton.d.ts +2 -2
- package/components/CircularButton/CircularButton.js +2 -1
- package/components/CodeDiff/CodeDiff.css +2 -2
- package/components/ColorPicker/ColorPicker.css +5 -4
- package/components/ColourSwatch/ColourSwatch.css +1 -1
- package/components/CommandPalette/CommandPalette.d.ts +13 -1
- package/components/CommandPalette/CommandPalette.js +12 -13
- package/components/CommandPaletteTrigger/CommandPaletteTrigger.css +1 -1
- package/components/CommandPaletteTrigger/CommandPaletteTrigger.d.ts +11 -1
- package/components/CommandPaletteTrigger/CommandPaletteTrigger.js +14 -5
- package/components/Composer/Composer.css +4 -4
- package/components/Composer/Composer.d.ts +2 -0
- package/components/Composer/Composer.js +6 -2
- package/components/ContactCard/ContactCard.css +1 -1
- package/components/ContactCard/ContactCard.js +4 -3
- package/components/ContextMenu/ContextMenu.css +10 -7
- package/components/ContextMenu/ContextMenu.d.ts +3 -0
- package/components/ContextMenu/ContextMenu.js +15 -9
- package/components/ContributionGraph/ContributionGraph.css +2 -2
- package/components/ContributionGraph/ContributionGraph.d.ts +7 -1
- package/components/ContributionGraph/ContributionGraph.js +6 -4
- package/components/DataTable/DataTable.css +1 -1
- package/components/DataTable/DataTable.d.ts +9 -3
- package/components/DataTable/DataTable.js +39 -29
- package/components/DataTable/columnTypes.d.ts +2 -2
- package/components/DataTable/columnTypes.js +9 -9
- package/components/DateInput/DateInput.css +4 -4
- package/components/DatePicker/DatePicker.d.ts +23 -7
- package/components/DatePicker/DatePicker.js +41 -34
- package/components/Dialog/Dialog.css +1 -1
- package/components/DocumentChip/DocumentChip.css +1 -1
- package/components/DocumentChip/DocumentChip.js +2 -1
- package/components/Drawer/Drawer.css +46 -12
- package/components/Drawer/Drawer.d.ts +12 -2
- package/components/Drawer/Drawer.js +12 -1
- package/components/Dropdown/ChosenMark.css +1 -1
- package/components/Dropdown/Dropdown.css +1 -1
- package/components/Dropdown/Dropdown.js +4 -3
- package/components/DropdownMenu/DropdownMenu.css +8 -5
- package/components/DropdownMenu/DropdownMenu.d.ts +6 -1
- package/components/DropdownMenu/DropdownMenu.js +15 -9
- package/components/DropdownMenu/ShortcutHint.d.ts +12 -0
- package/components/DropdownMenu/ShortcutHint.js +17 -0
- package/components/EmptyState/EmptyState.js +2 -1
- package/components/EntityCard/EntityCard.js +2 -1
- package/components/EventCalendar/EventCalendar.css +1 -1
- package/components/EventCalendar/EventCalendar.d.ts +21 -6
- package/components/EventCalendar/EventCalendar.js +33 -31
- package/components/FileInput/FileInput.d.ts +2 -0
- package/components/FileInput/FileInput.js +4 -1
- package/components/FilterBar/FilterBar.js +2 -1
- package/components/FunnelChart/FunnelChart.d.ts +2 -0
- package/components/FunnelChart/FunnelChart.js +11 -7
- package/components/GanttChart/GanttChart.css +1 -0
- package/components/GanttChart/GanttChart.js +2 -1
- package/components/Gauge/Gauge.css +1 -1
- package/components/Gauge/Gauge.d.ts +2 -0
- package/components/Gauge/Gauge.js +3 -1
- package/components/Globe/Globe.css +1 -0
- package/components/HoverCard/HoverCard.css +1 -0
- package/components/HoverCard/HoverCard.d.ts +2 -2
- package/components/ImageCompare/ImageCompare.css +1 -0
- package/components/Input/Input.css +8 -8
- package/components/Input/Input.js +3 -2
- package/components/Inspector/Inspector.css +12 -11
- package/components/Inspector/InspectorSlider.d.ts +2 -0
- package/components/Inspector/InspectorSlider.js +8 -1
- package/components/Instructions/Instructions.css +1 -1
- package/components/Instructions/Instructions.js +2 -1
- package/components/InterruptCard/InterruptCard.css +1 -1
- package/components/InterruptCard/InterruptCard.js +2 -1
- package/components/Kbd/Kbd.css +21 -0
- package/components/Kbd/Kbd.d.ts +14 -4
- package/components/Kbd/Kbd.js +10 -1
- package/components/LegendTile/LegendTile.d.ts +6 -1
- package/components/LegendTile/LegendTile.js +3 -2
- package/components/Lightbox/Lightbox.css +3 -3
- package/components/Lightbox/Lightbox.js +6 -14
- package/components/LinkList/LinkList.js +3 -2
- package/components/MapCallout/MapCallout.css +1 -0
- package/components/MegaMenu/MegaMenu.css +271 -0
- package/components/MegaMenu/MegaMenu.d.ts +72 -0
- package/components/MegaMenu/MegaMenu.js +71 -0
- package/components/Mention/Mention.js +2 -1
- package/components/MessageActions/MessageActions.js +2 -1
- package/components/MessageCard/MessageCard.css +1 -1
- package/components/MessageCard/MessageCard.js +2 -1
- package/components/Meter/Meter.css +1 -1
- package/components/Meter/Meter.d.ts +2 -0
- package/components/Meter/Meter.js +3 -1
- package/components/MotionSwatch/MotionSwatch.css +3 -3
- package/components/Nav/Nav.d.ts +7 -2
- package/components/NavList/NavList.css +3 -3
- package/components/NotificationCenter/NotificationCenter.d.ts +2 -0
- package/components/NotificationCenter/NotificationCenter.js +7 -2
- package/components/Pagination/Pagination.d.ts +2 -0
- package/components/Pagination/Pagination.js +6 -2
- package/components/PinInput/PinInput.js +3 -2
- package/components/Popover/Popover.d.ts +2 -2
- package/components/ProgressBar/ProgressBar.css +1 -1
- package/components/ProgressBar/ProgressBar.d.ts +2 -0
- package/components/ProgressBar/ProgressBar.js +3 -5
- package/components/ProgressiveBlur/ProgressiveBlur.css +102 -0
- package/components/ProgressiveBlur/ProgressiveBlur.d.ts +30 -0
- package/components/ProgressiveBlur/ProgressiveBlur.js +27 -0
- package/components/PromptSuggestions/PromptSuggestions.css +11 -4
- package/components/PromptSuggestions/PromptSuggestions.js +3 -1
- package/components/Prose/Prose.css +3 -0
- package/components/QuestionCard/QuestionCard.css +4 -3
- package/components/QuestionCard/QuestionCard.js +2 -8
- package/components/Quote/Quote.css +4 -4
- package/components/RadioButton/RadioButton.css +5 -3
- package/components/Rating/Rating.js +4 -3
- package/components/Reasoning/Reasoning.css +8 -8
- package/components/Reasoning/Reasoning.js +1 -0
- package/components/RichDropdown/RichDropdown.css +4 -3
- package/components/SegmentedControl/SegmentedControl.css +1 -1
- package/components/SegmentedControl/SegmentedControl.js +5 -3
- package/components/SidebarPanel/SidebarPanel.css +7 -7
- package/components/SidebarPanel/SidebarPanel.js +2 -2
- package/components/SiteHeader/SiteHeader.css +305 -0
- package/components/SiteHeader/SiteHeader.d.ts +117 -0
- package/components/SiteHeader/SiteHeader.js +329 -0
- package/components/Skeleton/Skeleton.css +2 -2
- package/components/Skeleton/Skeleton.js +1 -0
- package/components/Slider/Slider.css +16 -1
- package/components/Slider/Slider.js +1 -1
- package/components/SourceChip/SourceChip.d.ts +2 -0
- package/components/SourceChip/SourceChip.js +6 -2
- package/components/SourceTrail/SourceTrail.css +1 -1
- package/components/SpacingSwatch/SpacingSwatch.css +3 -3
- package/components/SplitButton/SplitButton.css +2 -2
- package/components/SplitButton/SplitButton.js +1 -0
- package/components/SplitPane/SplitPane.js +3 -2
- package/components/Stat/Stat.css +1 -1
- package/components/Stat/Stat.d.ts +2 -0
- package/components/Stat/Stat.js +3 -1
- package/components/Stepper/Stepper.css +5 -5
- package/components/Stepper/Stepper.d.ts +2 -0
- package/components/Stepper/Stepper.js +5 -1
- package/components/StreamingText/StreamingText.css +1 -1
- package/components/Table/Table.css +2 -2
- package/components/Table/Table.d.ts +2 -2
- package/components/Table/Table.js +4 -2
- package/components/TableToolbar/TableToolbar.js +2 -1
- package/components/Tabs/Tabs.js +3 -2
- package/components/ThreadPanel/ThreadPanel.css +19 -5
- package/components/ThreadPanel/ThreadPanel.d.ts +6 -1
- package/components/ThreadPanel/ThreadPanel.js +11 -7
- package/components/ThreadTabs/ThreadTabs.css +8 -1
- package/components/ThreadTabs/ThreadTabs.js +4 -2
- package/components/TimePicker/TimePicker.css +1 -1
- package/components/TimePicker/TimePicker.d.ts +17 -4
- package/components/TimePicker/TimePicker.js +21 -3
- package/components/Timeline/Timeline.css +8 -8
- package/components/Timeline/Timeline.d.ts +2 -0
- package/components/Timeline/Timeline.js +5 -2
- package/components/Toast/Toast.css +5 -5
- package/components/Toast/Toast.js +3 -2
- package/components/ToggleGroup/ToggleGroup.css +1 -1
- package/components/ToggleSwitch/ToggleSwitch.css +5 -5
- package/components/ToggleSwitch/ToggleSwitch.js +1 -0
- package/components/ToolCall/ToolCall.css +3 -3
- package/components/Toolbar/Toolbar.js +4 -2
- package/components/Tooltip/Tooltip.css +1 -0
- package/components/Tooltip/Tooltip.d.ts +2 -2
- package/components/TopAppBar/TopAppBar.css +2 -2
- package/components/TopAppBar/TopAppBar.js +3 -1
- package/components/TreeView/TreeView.css +1 -1
- package/components/TreeView/TreeView.js +4 -3
- package/components/UsageCard/UsageCard.d.ts +2 -0
- package/components/UsageCard/UsageCard.js +3 -0
- package/components/Widget/Widget.css +1 -1
- package/components/Widget/Widget.d.ts +2 -0
- package/components/Widget/Widget.js +3 -2
- package/components/WorldMap/WorldMap.css +1 -0
- package/components/registry.json +26 -2
- package/components/registry.json.d.ts +26 -2
- package/components/registry.json.js +1 -1
- package/fonts/material-symbols.css +13 -1
- package/fonts/presets/anton-400-latin-ext.woff2 +0 -0
- package/fonts/presets/anton-400-latin.woff2 +0 -0
- package/fonts/presets/work-sans-300-700-latin-ext.woff2 +0 -0
- package/fonts/presets/work-sans-300-700-latin.woff2 +0 -0
- package/index.d.ts +4 -0
- package/index.js +9 -0
- package/package.json +9 -1
- package/tokens/motion.d.ts +2 -0
- package/tokens/motion.js +2 -0
- package/tokens/presets/marquee.css +299 -0
- package/tokens/presets/presets.css +1 -0
- package/tokens/registry.json +6 -0
- package/tokens/registry.json.d.ts +6 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +18 -0
- package/tokens/tokens-light.css +18 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ An open source React design system built for AI products and coding agents: comp
|
|
|
13
13
|
|
|
14
14
|
## Why Rift DS
|
|
15
15
|
|
|
16
|
-
- **<!-- component-count -->
|
|
16
|
+
- **<!-- component-count -->162<!-- /component-count --> components.** Buttons, forms and tables through to charts, navigation and overlays, each typed and documented with live examples. Every one forwards a ref and your own attributes to its element, state you can own works controlled or uncontrolled (a modal opens when your app says so, by design), and every word a component writes has a prop you can localise. The build checks the first two on every component, and the words wherever they sit in markup. React and React DOM are the only required peer dependencies.
|
|
17
17
|
- **AI patterns, the whole chat and templates, ready to use.** Composer, streaming chat thread, tool calls, reasoning and thread panel are in the box, the complete AI chat ships assembled as one widget that one command scaffolds, and the docs site has complete template screens built from them.
|
|
18
18
|
- **Fully themeable.** The shipped themes are complete looks to start from, each in light and dark. One `data-brand` attribute applies a theme, and a few CSS overrides make your own.
|
|
19
19
|
- **Ready for coding agents.** One command installs the agent skills, and a public MCP endpoint serves every component's props, so your agent builds with the library as it actually is. No key, no account.
|
|
@@ -91,7 +91,7 @@ Every component's prop contract is also plain markdown (append `.md` to its docs
|
|
|
91
91
|
|
|
92
92
|
## Theming
|
|
93
93
|
|
|
94
|
-
Theming is CSS custom properties; there is no configuration API and no provider (one exception: wrap your tree in `ToastProvider` if, and only if, you use the toast queue via `useToast`). Complete looks ship as generated stylesheets: import the aggregate once and one attribute rethemes everything, light and dark, typefaces included (each preset self-hosts its faces).
|
|
94
|
+
Theming is CSS custom properties; there is no configuration API and no theme provider (one required exception: wrap your tree in `ToastProvider` if, and only if, you use the toast queue via `useToast`). Two providers are optional: `LinkProvider` hands every component your router's link, and `LocaleProvider` sets the locale numbers, dates and calendars are written in (`en-US` without it, never the browser's). Key bindings go through `useShortcut` from `rift-ds/behaviors/shortcuts`, the listener the components use, so a shortcut stays out of text fields and open overlays. For a right-to-left language, set `dir="rtl"` on `<html>`: every component lays itself out from it. Complete looks ship as generated stylesheets: import the aggregate once and one attribute rethemes everything, light and dark, typefaces included (each preset self-hosts its faces).
|
|
95
95
|
|
|
96
96
|
```tsx
|
|
97
97
|
import 'rift-ds/tokens/presets/presets.css';
|
|
@@ -107,13 +107,13 @@ import 'rift-ds/tokens/presets/presets.css';
|
|
|
107
107
|
|
|
108
108
|
## Components
|
|
109
109
|
|
|
110
|
-
<!-- component-count -->
|
|
110
|
+
<!-- component-count -->162<!-- /component-count --> components, including a chat set for AI products (Chat thread, Composer, Tool call, Reasoning, Thread panel) and Shader field, a WebGL2 ambient background whose light sources read your colour tokens at runtime, so it re-themes with everything else. Details and live examples are on the [docs site](https://rift-ds.com/components).
|
|
111
111
|
|
|
112
112
|
<details>
|
|
113
113
|
<summary>The full list</summary>
|
|
114
114
|
|
|
115
115
|
<!-- component-list:start -->
|
|
116
|
-
Accordion · Agent plan · Agent rail · Agent status · AI button · Alert · Alert dialog · Anchor nav · Animated number · App layout · App sidebar · Area chart · Artifact card · Artifact panel · Attachment drop zone · Attachment group · Attachment tile · Attachment viewer · Avatar · Avatar group · Badge · Banner · Bar chart · Bento tile · Bento tile art · Bottom nav · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combo chart · Combobox · Command palette · Command palette trigger · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Filter bar · Funnel chart · Gantt chart · Gauge · Globe · Hover card · Image compare · Input · Inspector colour picker · Inspector dropdown · Inspector input · Inspector section · Inspector segments · Inspector slider · Inspector toggle switch · Instructions · Interrupt card · Kbd · Legend tile · Lightbox · Line chart · Link list · Map callout · Map legend · Mention · Message actions · Message card · Meter · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Panel · Pie chart · Pin input · Pixel avatar · Popover · Progress bar · Prompt suggestions · Prose · Question card · Quote · Radar chart · Radial chart · Radio button · Rating · Reasoning · Rich dropdown · Scatter chart · Section title · Segmented control · Selection card · Shader field · Sidebar panel · Skeleton · Slider · Source chip · Source trail · Sparkline · Spinner · Split button · Split pane · Stacked bar chart · Stat · Status dot · Stepper · Streaming text · Swatch · Table · Table toolbar · Tabs · Tag input · Textarea · Theme picker · Theme toggle · Thread panel · Thread tabs · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Toolbar · Tooltip · Top app bar · Tree view · Treemap · Usage card · Waveform · Widget · World map
|
|
116
|
+
Accordion · Agent plan · Agent rail · Agent status · AI button · Alert · Alert dialog · Anchor nav · Animated number · App layout · App sidebar · Area chart · Artifact card · Artifact panel · Attachment drop zone · Attachment group · Attachment tile · Attachment viewer · Avatar · Avatar group · Badge · Banner · Bar chart · Bento tile · Bento tile art · Bottom nav · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combo chart · Combobox · Command palette · Command palette trigger · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Filter bar · Funnel chart · Gantt chart · Gauge · Globe · Hover card · Image compare · Input · Inspector colour picker · Inspector dropdown · Inspector input · Inspector section · Inspector segments · Inspector slider · Inspector toggle switch · Instructions · Interrupt card · Kbd · Legend tile · Lightbox · Line chart · Link list · Map callout · Map legend · Mega menu · Mention · Message actions · Message card · Meter · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Panel · Pie chart · Pin input · Pixel avatar · Popover · Progress bar · Progressive blur · Prompt suggestions · Prose · Question card · Quote · Radar chart · Radial chart · Radio button · Rating · Reasoning · Rich dropdown · Scatter chart · Section title · Segmented control · Selection card · Shader field · Sidebar panel · Site header · Skeleton · Slider · Source chip · Source trail · Sparkline · Spinner · Split button · Split pane · Stacked bar chart · Stat · Status dot · Stepper · Streaming text · Swatch · Table · Table toolbar · Tabs · Tag input · Textarea · Theme picker · Theme toggle · Thread panel · Thread tabs · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Toolbar · Tooltip · Top app bar · Tree view · Treemap · Usage card · Waveform · Widget · World map
|
|
117
117
|
<!-- component-list:end -->
|
|
118
118
|
|
|
119
119
|
</details>
|
|
@@ -27,6 +27,9 @@ export interface LinkProviderProps {
|
|
|
27
27
|
* read a provider and take the prop alone (design.md's Components intro
|
|
28
28
|
* names them, and scripts/validate-component-contract.mjs keeps them off
|
|
29
29
|
* the provider).
|
|
30
|
+
*
|
|
31
|
+
* Published as the `./behaviors/LinkProvider` subpath because consumers
|
|
32
|
+
* mount it; with the scroll lock it is one of only two behaviors that are.
|
|
30
33
|
*/
|
|
31
34
|
export declare function LinkProvider({ linkComponent, children }: LinkProviderProps): React.JSX.Element;
|
|
32
35
|
/**
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export interface LocaleProviderProps {
|
|
3
|
+
/**
|
|
4
|
+
* The BCP 47 locale every rift-ds component under this provider formats
|
|
5
|
+
* numbers, dates and calendars in, such as `de-DE`. Named like the
|
|
6
|
+
* instance prop it stands in for.
|
|
7
|
+
*/
|
|
8
|
+
locale: string;
|
|
9
|
+
/** The part of the app it applies to, usually the whole app. */
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Sets the locale for every client-rendered rift-ds component below it that
|
|
14
|
+
* formats a number or a date, or lays out a calendar: grouping and decimal
|
|
15
|
+
* marks, month and weekday names, the first day of the week, and the
|
|
16
|
+
* 12- or 24-hour clock. A component's own `locale` prop wins over it. The
|
|
17
|
+
* components that can render on the server cannot read a provider and take
|
|
18
|
+
* the prop alone. Words stay props of their own: the locale formats, it
|
|
19
|
+
* never translates.
|
|
20
|
+
*/
|
|
21
|
+
export declare function LocaleProvider({ locale, children }: LocaleProviderProps): React.JSX.Element;
|
|
22
|
+
/**
|
|
23
|
+
* The locale to format in: the component's own `locale` prop, else the
|
|
24
|
+
* nearest LocaleProvider's, else `en-US`. Never the runtime's default, so
|
|
25
|
+
* the server and the browser always agree.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useLocale(override?: string): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
import { DEFAULT_LOCALE } from "./locale.js";
|
|
5
|
+
const LocaleContext = createContext(null);
|
|
6
|
+
function LocaleProvider({ locale, children }) {
|
|
7
|
+
return /* @__PURE__ */ jsx(LocaleContext.Provider, { value: locale, children });
|
|
8
|
+
}
|
|
9
|
+
function useLocale(override) {
|
|
10
|
+
const provided = useContext(LocaleContext);
|
|
11
|
+
return override ?? provided ?? DEFAULT_LOCALE;
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
LocaleProvider,
|
|
15
|
+
useLocale
|
|
16
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
READING DIRECTION SIGN
|
|
3
|
+
`--ds-inline-sign` is 1 in a left-to-right context and -1 in a
|
|
4
|
+
right-to-left one, read from the element's own direction (`:dir()`),
|
|
5
|
+
so a `dir` attribute anywhere up the tree sets it. A component that
|
|
6
|
+
moves along the inline axis (a slide-in, a thumb, a shimmer's sweep, an
|
|
7
|
+
optical nudge) multiplies its offset by it, always with the fallback
|
|
8
|
+
`var(--ds-inline-sign, 1)`, and imports this file. Logical properties
|
|
9
|
+
cover placement; this covers motion, which CSS has no logical form for.
|
|
10
|
+
design.md's "Layout follows the reading direction" owns the contract.
|
|
11
|
+
============================================ */
|
|
12
|
+
|
|
13
|
+
:dir(ltr) {
|
|
14
|
+
--ds-inline-sign: 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
:dir(rtl) {
|
|
18
|
+
--ds-inline-sign: -1;
|
|
19
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reading direction, for the code that moves along the inline axis:
|
|
3
|
+
* arrow keys, drags and anything placed in pixels. Stylesheets read it
|
|
4
|
+
* through logical properties and `--ds-inline-sign` (direction.css); script
|
|
5
|
+
* reads it here, from the element itself at the moment it acts, so a `dir`
|
|
6
|
+
* attribute anywhere up the tree applies and a change to it needs no
|
|
7
|
+
* re-render. No React and no browser API at import time, so a server
|
|
8
|
+
* component can import it too. design.md's "Layout follows the reading
|
|
9
|
+
* direction" owns the contract.
|
|
10
|
+
*/
|
|
11
|
+
/** Whether `element` lays out right to left. False on the server and for a missing element. */
|
|
12
|
+
export declare function isRtl(element: Element | null | undefined): boolean;
|
|
13
|
+
/** 1 when `element` reads left to right, -1 when it reads right to left: the sign to multiply a horizontal pointer delta by. */
|
|
14
|
+
export declare const inlineSign: (element: Element | null | undefined) => 1 | -1;
|
|
15
|
+
/**
|
|
16
|
+
* An arrow key along the inline axis as a step through the reading order:
|
|
17
|
+
* `1` for the next item (ArrowRight in English, ArrowLeft in Arabic), `-1`
|
|
18
|
+
* for the previous one, and 0 for any other key.
|
|
19
|
+
*/
|
|
20
|
+
export declare function inlineStep(key: string, element: Element | null | undefined): -1 | 0 | 1;
|
|
21
|
+
/**
|
|
22
|
+
* The Material Symbols ligatures that point along the reading direction and
|
|
23
|
+
* so mirror in a right-to-left context: a "next" chevron points left in
|
|
24
|
+
* Arabic. A component that draws one adds the `ds-mirror-rtl` class (the
|
|
25
|
+
* rule lives in src/fonts/material-symbols.css, which every icon imports);
|
|
26
|
+
* `mirrorClass` decides it for an icon a consumer passes by name.
|
|
27
|
+
*/
|
|
28
|
+
export declare const DIRECTIONAL_ICONS: Set<string>;
|
|
29
|
+
/** Whether the icon named `name` points along the reading direction and should mirror in a right-to-left context. */
|
|
30
|
+
export declare const mirrorsInRtl: (name: unknown) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* A key as it would be pressed in a left-to-right layout: ArrowLeft and
|
|
33
|
+
* ArrowRight swap when `element` reads right to left, and every other key
|
|
34
|
+
* passes through. A component that steps along the inline axis switches on
|
|
35
|
+
* this instead of the raw key, so "the next tab" is ArrowRight in English
|
|
36
|
+
* and ArrowLeft in Arabic with no second branch.
|
|
37
|
+
*/
|
|
38
|
+
export declare function logicalArrowKey(key: string, element: Element | null | undefined): string;
|
|
39
|
+
/** `ds-mirror-rtl` when the icon named `name` mirrors in a right-to-left context, else an empty string, for a className. */
|
|
40
|
+
export declare const mirrorClass: (name: unknown) => string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
function isRtl(element) {
|
|
2
|
+
if (!element || typeof getComputedStyle === "undefined") return false;
|
|
3
|
+
return getComputedStyle(element).direction === "rtl";
|
|
4
|
+
}
|
|
5
|
+
const inlineSign = (element) => isRtl(element) ? -1 : 1;
|
|
6
|
+
const DIRECTIONAL_ICONS = /* @__PURE__ */ new Set([
|
|
7
|
+
"chevron_left",
|
|
8
|
+
"chevron_right",
|
|
9
|
+
"arrow_back",
|
|
10
|
+
"arrow_forward",
|
|
11
|
+
"arrow_back_ios",
|
|
12
|
+
"arrow_forward_ios",
|
|
13
|
+
"arrow_back_ios_new",
|
|
14
|
+
"first_page",
|
|
15
|
+
"last_page",
|
|
16
|
+
"keyboard_arrow_left",
|
|
17
|
+
"keyboard_arrow_right",
|
|
18
|
+
"keyboard_double_arrow_left",
|
|
19
|
+
"keyboard_double_arrow_right",
|
|
20
|
+
"navigate_before",
|
|
21
|
+
"navigate_next",
|
|
22
|
+
"east",
|
|
23
|
+
"west",
|
|
24
|
+
"trending_flat",
|
|
25
|
+
"subdirectory_arrow_right",
|
|
26
|
+
"subdirectory_arrow_left",
|
|
27
|
+
"reply",
|
|
28
|
+
"forward",
|
|
29
|
+
"undo",
|
|
30
|
+
"redo",
|
|
31
|
+
"logout",
|
|
32
|
+
"login",
|
|
33
|
+
"send",
|
|
34
|
+
"menu_open",
|
|
35
|
+
"format_indent_increase",
|
|
36
|
+
"format_indent_decrease",
|
|
37
|
+
"view_sidebar",
|
|
38
|
+
"left_panel_open",
|
|
39
|
+
"left_panel_close",
|
|
40
|
+
"right_panel_open",
|
|
41
|
+
"right_panel_close"
|
|
42
|
+
]);
|
|
43
|
+
const mirrorsInRtl = (name) => typeof name === "string" && DIRECTIONAL_ICONS.has(name);
|
|
44
|
+
function logicalArrowKey(key, element) {
|
|
45
|
+
if (key !== "ArrowLeft" && key !== "ArrowRight" || !isRtl(element)) return key;
|
|
46
|
+
return key === "ArrowLeft" ? "ArrowRight" : "ArrowLeft";
|
|
47
|
+
}
|
|
48
|
+
const mirrorClass = (name) => mirrorsInRtl(name) ? "ds-mirror-rtl" : "";
|
|
49
|
+
export {
|
|
50
|
+
DIRECTIONAL_ICONS,
|
|
51
|
+
inlineSign,
|
|
52
|
+
isRtl,
|
|
53
|
+
logicalArrowKey,
|
|
54
|
+
mirrorClass,
|
|
55
|
+
mirrorsInRtl
|
|
56
|
+
};
|
package/behaviors/focusable.d.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Shared focusability queries for the overlay behavior layer.
|
|
3
3
|
*
|
|
4
4
|
* One home for the selector the modal components previously each carried a
|
|
5
|
-
* copy of. Plain DOM utilities — no React, safe to import anywhere.
|
|
5
|
+
* copy of. Plain DOM utilities — no React, safe to import anywhere. It
|
|
6
|
+
* serves non-overlay keyboard work too: Toolbar's arrow-key model reads it.
|
|
6
7
|
*/
|
|
7
8
|
/** Everything a focus trap must cycle through. */
|
|
8
9
|
export declare const FOCUSABLE = "button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale data every formatting component shares. Server-safe: no React and
|
|
3
|
+
* no browser API, so a component that renders from a Server Component can
|
|
4
|
+
* import it too. The client half (the provider and the hook) is
|
|
5
|
+
* LocaleProvider.tsx.
|
|
6
|
+
*
|
|
7
|
+
* The rule it carries: a component never formats in the runtime's default
|
|
8
|
+
* locale. The server and the browser can disagree on it, which prints
|
|
9
|
+
* `1,234` on one and `1.234` on the other and breaks hydration, and a
|
|
10
|
+
* consumer could not choose it. Every number, date and calendar convention
|
|
11
|
+
* a component prints is resolved from a locale the consumer can set, which
|
|
12
|
+
* falls back to DEFAULT_LOCALE. scripts/validate-locale-formatting.mjs
|
|
13
|
+
* enforces it.
|
|
14
|
+
*/
|
|
15
|
+
/** The locale a component formats in when neither its `locale` prop nor a LocaleProvider sets one. */
|
|
16
|
+
export declare const DEFAULT_LOCALE = "en-US";
|
|
17
|
+
/** A day of the week as `Date.getDay()` numbers it: 0 is Sunday, 6 is Saturday. */
|
|
18
|
+
export type Weekday = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
19
|
+
/**
|
|
20
|
+
* The first day of the week in `locale`, as a `Date.getDay()` number:
|
|
21
|
+
* Sunday (0) in en-US, Monday (1) in de-DE, Saturday (6) in ar-EG. Read
|
|
22
|
+
* from the runtime's CLDR week data where it has it, else from the region.
|
|
23
|
+
*/
|
|
24
|
+
export declare function weekStartOf(locale: string): Weekday;
|
|
25
|
+
/** A Sunday-first list of seven, reordered to start on `start`. */
|
|
26
|
+
export declare function rotateWeek<T>(sundayFirst: readonly T[], start: Weekday): T[];
|
|
27
|
+
/** How many days `day` sits after the week's first day: 0 for the first column, 6 for the last. */
|
|
28
|
+
export declare const weekdayOffset: (day: number, start: Weekday) => number;
|
|
29
|
+
/** The seven weekday names in `locale`, Sunday first, at the given width. */
|
|
30
|
+
export declare function weekdayNamesIn(locale: string, width: 'long' | 'short' | 'narrow'): string[];
|
|
31
|
+
/** Whether `locale` is written in English, the language every built-in label defaults to. */
|
|
32
|
+
export declare function isEnglish(locale: string): boolean;
|
|
33
|
+
/** Whether `locale` writes times on a 12-hour clock (en-US) rather than a 24-hour one (en-GB, de-DE). */
|
|
34
|
+
export declare function uses12HourClock(locale: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* A number as `locale` writes it, grouped and with its own decimal mark:
|
|
37
|
+
* `1,234.5` in en-US, `1.234,5` in de-DE. Formats are cached per locale.
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatNumber(value: number, locale: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* A number on a chart's value axis as `locale` writes it, grouped only from
|
|
42
|
+
* five digits up (`12,000` but `2024`), so a tick that happens to be a year
|
|
43
|
+
* never reads as a quantity.
|
|
44
|
+
*/
|
|
45
|
+
export declare function formatAxisNumber(value: number, locale: string): string;
|
|
46
|
+
/** A whole percentage, given as 0 to 100, as `locale` writes it: `64%` in en-US, `٦٤٪` in ar-EG, `64 %` in de-DE. */
|
|
47
|
+
export declare function formatPercent(percent: number, locale: string): string;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const DEFAULT_LOCALE = "en-US";
|
|
2
|
+
const SUNDAY_FIRST = new Set(
|
|
3
|
+
"AG AS BD BR BS BT BW BZ CA CN CO DM DO ET GT GU HK HN ID IL IN JM JP KE KH KR LA MH MM MO MT MX MZ NI NP PA PE PH PK PR PT PY SA SG SV TH TT TW UM US VE VI WS YE ZA ZW".split(" ")
|
|
4
|
+
);
|
|
5
|
+
const SATURDAY_FIRST = new Set("AE AF BH DJ DZ EG IQ IR JO KW LY OM QA SD SY".split(" "));
|
|
6
|
+
const weekStartCache = /* @__PURE__ */ new Map();
|
|
7
|
+
function weekStartOf(locale) {
|
|
8
|
+
const cached = weekStartCache.get(locale);
|
|
9
|
+
if (cached !== void 0) return cached;
|
|
10
|
+
let start = 1;
|
|
11
|
+
try {
|
|
12
|
+
const intlLocale = new Intl.Locale(locale);
|
|
13
|
+
const info = intlLocale.getWeekInfo?.() ?? intlLocale.weekInfo;
|
|
14
|
+
if (info) {
|
|
15
|
+
start = info.firstDay % 7;
|
|
16
|
+
} else {
|
|
17
|
+
const region = intlLocale.maximize().region ?? "";
|
|
18
|
+
start = SUNDAY_FIRST.has(region) ? 0 : SATURDAY_FIRST.has(region) ? 6 : 1;
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
start = 1;
|
|
22
|
+
}
|
|
23
|
+
weekStartCache.set(locale, start);
|
|
24
|
+
return start;
|
|
25
|
+
}
|
|
26
|
+
function rotateWeek(sundayFirst, start) {
|
|
27
|
+
return [...sundayFirst.slice(start), ...sundayFirst.slice(0, start)];
|
|
28
|
+
}
|
|
29
|
+
const weekdayOffset = (day, start) => (day - start + 7) % 7;
|
|
30
|
+
function weekdayNamesIn(locale, width) {
|
|
31
|
+
const format = new Intl.DateTimeFormat(locale, { weekday: width, timeZone: "UTC" });
|
|
32
|
+
return Array.from({ length: 7 }, (_, day) => format.format(Date.UTC(2023, 0, 1 + day)));
|
|
33
|
+
}
|
|
34
|
+
function isEnglish(locale) {
|
|
35
|
+
try {
|
|
36
|
+
return new Intl.Locale(locale).language === "en";
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function uses12HourClock(locale) {
|
|
42
|
+
const cycle = new Intl.DateTimeFormat(locale, { hour: "numeric" }).resolvedOptions().hourCycle;
|
|
43
|
+
return cycle === "h11" || cycle === "h12";
|
|
44
|
+
}
|
|
45
|
+
const numberFormats = /* @__PURE__ */ new Map();
|
|
46
|
+
function formatNumber(value, locale) {
|
|
47
|
+
let format = numberFormats.get(locale);
|
|
48
|
+
if (!format) {
|
|
49
|
+
format = new Intl.NumberFormat(locale, { maximumFractionDigits: 3 });
|
|
50
|
+
numberFormats.set(locale, format);
|
|
51
|
+
}
|
|
52
|
+
return format.format(value);
|
|
53
|
+
}
|
|
54
|
+
const axisFormats = /* @__PURE__ */ new Map();
|
|
55
|
+
function formatAxisNumber(value, locale) {
|
|
56
|
+
let format = axisFormats.get(locale);
|
|
57
|
+
if (!format) {
|
|
58
|
+
format = new Intl.NumberFormat(locale, {
|
|
59
|
+
maximumFractionDigits: 3,
|
|
60
|
+
// 'min2' is CLDR's grouping for short numbers; the type lags the spec.
|
|
61
|
+
useGrouping: "min2"
|
|
62
|
+
});
|
|
63
|
+
axisFormats.set(locale, format);
|
|
64
|
+
}
|
|
65
|
+
return format.format(value);
|
|
66
|
+
}
|
|
67
|
+
const percentFormats = /* @__PURE__ */ new Map();
|
|
68
|
+
function formatPercent(percent, locale) {
|
|
69
|
+
let format = percentFormats.get(locale);
|
|
70
|
+
if (!format) {
|
|
71
|
+
format = new Intl.NumberFormat(locale, { style: "percent", maximumFractionDigits: 0 });
|
|
72
|
+
percentFormats.set(locale, format);
|
|
73
|
+
}
|
|
74
|
+
return format.format(percent / 100);
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
DEFAULT_LOCALE,
|
|
78
|
+
formatAxisNumber,
|
|
79
|
+
formatNumber,
|
|
80
|
+
formatPercent,
|
|
81
|
+
isEnglish,
|
|
82
|
+
rotateWeek,
|
|
83
|
+
uses12HourClock,
|
|
84
|
+
weekStartOf,
|
|
85
|
+
weekdayNamesIn,
|
|
86
|
+
weekdayOffset
|
|
87
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shortcut grammar and how a shortcut is drawn, with no React and no
|
|
3
|
+
* browser API, so a component that renders from a Server Component can
|
|
4
|
+
* draw or announce a shortcut too. The listener and the hooks are
|
|
5
|
+
* shortcuts.ts, which owns the grammar's description.
|
|
6
|
+
*/
|
|
7
|
+
/** A shortcut in the grammar above, such as `mod+k`, `shift+?` or `g i`. */
|
|
8
|
+
export type Shortcut = string;
|
|
9
|
+
/** The platform a shortcut is read and drawn for: `apple` maps `mod` to ⌘, `other` to Ctrl. */
|
|
10
|
+
export type ShortcutPlatform = 'apple' | 'other';
|
|
11
|
+
/** The legends a shortcut is drawn with, by modifier or key name. Merged over the platform's defaults. */
|
|
12
|
+
export type ShortcutKeyLabels = Partial<Record<string, string>>;
|
|
13
|
+
/** One step of a shortcut: its key and the modifiers held with it. */
|
|
14
|
+
export type Step = {
|
|
15
|
+
key: string;
|
|
16
|
+
mod: boolean;
|
|
17
|
+
ctrl: boolean;
|
|
18
|
+
meta: boolean;
|
|
19
|
+
alt: boolean;
|
|
20
|
+
shift: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare const KEY_ALIASES: Record<string, string>;
|
|
23
|
+
/** A shortcut's steps, parsed once. A step written `ctrl++` would be ambiguous, so `plus` names the key. */
|
|
24
|
+
export declare const parseShortcut: (shortcut: Shortcut) => Step[];
|
|
25
|
+
/** The platform this code runs on, read from the browser. `other` on the server. */
|
|
26
|
+
export declare const detectPlatform: () => ShortcutPlatform;
|
|
27
|
+
/**
|
|
28
|
+
* A shortcut as keycap legends, one array per step: `mod+shift+p` is
|
|
29
|
+
* `[['⇧', '⌘', 'P']]` on a Mac and `[['Ctrl', 'Shift', 'P']]` elsewhere.
|
|
30
|
+
* `labels` replaces any legend, such as `{ ctrl: 'Strg' }` for a German
|
|
31
|
+
* keyboard.
|
|
32
|
+
*/
|
|
33
|
+
export declare function shortcutKeys(shortcut: Shortcut, platform: ShortcutPlatform, labels?: ShortcutKeyLabels): string[][];
|
|
34
|
+
/**
|
|
35
|
+
* A shortcut as one line of text, for a menu row: `⇧⌘P` on a Mac and
|
|
36
|
+
* `Ctrl+Shift+P` elsewhere, the steps of a sequence spaced apart.
|
|
37
|
+
*/
|
|
38
|
+
export declare function formatShortcut(shortcut: Shortcut, platform: ShortcutPlatform, labels?: ShortcutKeyLabels): string;
|
|
39
|
+
/**
|
|
40
|
+
* A single-step shortcut as an `aria-keyshortcuts` value (`Meta+K` on a Mac,
|
|
41
|
+
* `Control+K` elsewhere). A sequence has no ARIA form, so it gives undefined.
|
|
42
|
+
*/
|
|
43
|
+
export declare function shortcutAria(shortcut: Shortcut, platform: ShortcutPlatform): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* A shortcut as an `aria-keyshortcuts` value for both platforms at once
|
|
46
|
+
* (`Meta+K Control+K` for `mod+k`), for markup that renders on the server or
|
|
47
|
+
* before the platform is known. Undefined for a sequence.
|
|
48
|
+
*/
|
|
49
|
+
export declare function shortcutAriaAnyPlatform(shortcut: Shortcut): string | undefined;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const KEY_ALIASES = {
|
|
2
|
+
esc: "escape",
|
|
3
|
+
return: "enter",
|
|
4
|
+
up: "arrowup",
|
|
5
|
+
down: "arrowdown",
|
|
6
|
+
left: "arrowleft",
|
|
7
|
+
right: "arrowright",
|
|
8
|
+
del: "delete",
|
|
9
|
+
plus: "+",
|
|
10
|
+
" ": "space",
|
|
11
|
+
cmd: "meta",
|
|
12
|
+
command: "meta",
|
|
13
|
+
option: "alt",
|
|
14
|
+
control: "ctrl"
|
|
15
|
+
};
|
|
16
|
+
const MODIFIERS = /* @__PURE__ */ new Set(["mod", "ctrl", "meta", "alt", "shift"]);
|
|
17
|
+
const parseStep = (text) => {
|
|
18
|
+
const step = { key: "", mod: false, ctrl: false, meta: false, alt: false, shift: false };
|
|
19
|
+
for (const raw of text.toLowerCase().split("+")) {
|
|
20
|
+
const part = KEY_ALIASES[raw] ?? raw;
|
|
21
|
+
if (MODIFIERS.has(part)) step[part] = true;
|
|
22
|
+
else step.key = part;
|
|
23
|
+
}
|
|
24
|
+
return step;
|
|
25
|
+
};
|
|
26
|
+
const parseShortcut = (shortcut) => shortcut.trim().split(/\s+/).filter(Boolean).map(parseStep);
|
|
27
|
+
const detectPlatform = () => {
|
|
28
|
+
if (typeof navigator === "undefined") return "other";
|
|
29
|
+
const nav = navigator;
|
|
30
|
+
const name = nav.userAgentData?.platform || nav.platform || nav.userAgent || "";
|
|
31
|
+
return /mac|iphone|ipad|ipod/i.test(name) ? "apple" : "other";
|
|
32
|
+
};
|
|
33
|
+
const APPLE_LABELS = {
|
|
34
|
+
mod: "⌘",
|
|
35
|
+
meta: "⌘",
|
|
36
|
+
ctrl: "⌃",
|
|
37
|
+
alt: "⌥",
|
|
38
|
+
shift: "⇧",
|
|
39
|
+
enter: "↩",
|
|
40
|
+
escape: "Esc",
|
|
41
|
+
backspace: "⌫",
|
|
42
|
+
delete: "⌦",
|
|
43
|
+
tab: "⇥",
|
|
44
|
+
space: "Space"
|
|
45
|
+
};
|
|
46
|
+
const OTHER_LABELS = {
|
|
47
|
+
mod: "Ctrl",
|
|
48
|
+
meta: "Meta",
|
|
49
|
+
ctrl: "Ctrl",
|
|
50
|
+
alt: "Alt",
|
|
51
|
+
shift: "Shift",
|
|
52
|
+
enter: "Enter",
|
|
53
|
+
escape: "Esc",
|
|
54
|
+
backspace: "Backspace",
|
|
55
|
+
delete: "Del",
|
|
56
|
+
tab: "Tab",
|
|
57
|
+
space: "Space"
|
|
58
|
+
};
|
|
59
|
+
const SHARED_LABELS = {
|
|
60
|
+
arrowup: "↑",
|
|
61
|
+
arrowdown: "↓",
|
|
62
|
+
arrowleft: "←",
|
|
63
|
+
arrowright: "→",
|
|
64
|
+
home: "Home",
|
|
65
|
+
end: "End",
|
|
66
|
+
pageup: "PgUp",
|
|
67
|
+
pagedown: "PgDn"
|
|
68
|
+
};
|
|
69
|
+
const APPLE_ORDER = ["ctrl", "alt", "shift", "mod", "meta"];
|
|
70
|
+
const OTHER_ORDER = ["mod", "ctrl", "meta", "alt", "shift"];
|
|
71
|
+
function shortcutKeys(shortcut, platform, labels = {}) {
|
|
72
|
+
const table = { ...SHARED_LABELS, ...platform === "apple" ? APPLE_LABELS : OTHER_LABELS };
|
|
73
|
+
const legend = (name) => labels[name] ?? table[name] ?? (/^f\d+$/.test(name) ? name.toUpperCase() : name.length === 1 ? name.toUpperCase() : name);
|
|
74
|
+
return parseShortcut(shortcut).map((step) => {
|
|
75
|
+
const order = platform === "apple" ? APPLE_ORDER : OTHER_ORDER;
|
|
76
|
+
const mods = order.filter((mod) => step[mod]);
|
|
77
|
+
const unique = mods.filter((mod, i) => !mods.slice(0, i).some((m) => legend(m) === legend(mod)));
|
|
78
|
+
return [...unique.map(legend), legend(step.key)];
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function formatShortcut(shortcut, platform, labels = {}) {
|
|
82
|
+
return shortcutKeys(shortcut, platform, labels).map((keys) => keys.join(platform === "apple" ? "" : "+")).join(" ");
|
|
83
|
+
}
|
|
84
|
+
const ARIA_KEYS = {
|
|
85
|
+
ctrl: "Control",
|
|
86
|
+
meta: "Meta",
|
|
87
|
+
alt: "Alt",
|
|
88
|
+
shift: "Shift",
|
|
89
|
+
escape: "Escape",
|
|
90
|
+
enter: "Enter",
|
|
91
|
+
space: "Space",
|
|
92
|
+
tab: "Tab",
|
|
93
|
+
backspace: "Backspace",
|
|
94
|
+
delete: "Delete",
|
|
95
|
+
arrowup: "ArrowUp",
|
|
96
|
+
arrowdown: "ArrowDown",
|
|
97
|
+
arrowleft: "ArrowLeft",
|
|
98
|
+
arrowright: "ArrowRight",
|
|
99
|
+
home: "Home",
|
|
100
|
+
end: "End",
|
|
101
|
+
pageup: "PageUp",
|
|
102
|
+
pagedown: "PageDown"
|
|
103
|
+
};
|
|
104
|
+
function shortcutAria(shortcut, platform) {
|
|
105
|
+
const steps = parseShortcut(shortcut);
|
|
106
|
+
if (steps.length !== 1) return void 0;
|
|
107
|
+
const [step] = steps;
|
|
108
|
+
const names = [
|
|
109
|
+
step.ctrl || step.mod && platform === "other" ? "Control" : null,
|
|
110
|
+
step.meta || step.mod && platform === "apple" ? "Meta" : null,
|
|
111
|
+
step.alt ? "Alt" : null,
|
|
112
|
+
step.shift ? "Shift" : null,
|
|
113
|
+
ARIA_KEYS[step.key] ?? (step.key.length === 1 ? step.key.toUpperCase() : step.key)
|
|
114
|
+
].filter(Boolean);
|
|
115
|
+
return names.join("+");
|
|
116
|
+
}
|
|
117
|
+
function shortcutAriaAnyPlatform(shortcut) {
|
|
118
|
+
const forms = [shortcutAria(shortcut, "apple"), shortcutAria(shortcut, "other")].filter(Boolean);
|
|
119
|
+
return forms.length > 0 ? [...new Set(forms)].join(" ") : void 0;
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
KEY_ALIASES,
|
|
123
|
+
detectPlatform,
|
|
124
|
+
formatShortcut,
|
|
125
|
+
parseShortcut,
|
|
126
|
+
shortcutAria,
|
|
127
|
+
shortcutAriaAnyPlatform,
|
|
128
|
+
shortcutKeys
|
|
129
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Shortcut, ShortcutPlatform } from './shortcut-keys';
|
|
2
|
+
export { detectPlatform, formatShortcut, parseShortcut, shortcutAria, shortcutKeys, type Shortcut, type ShortcutKeyLabels, type ShortcutPlatform, } from './shortcut-keys';
|
|
3
|
+
/**
|
|
4
|
+
* Keyboard shortcuts: the one document keydown listener every app-level key
|
|
5
|
+
* binding goes through, the grammar a shortcut is written in, and the way it
|
|
6
|
+
* is drawn on each platform.
|
|
7
|
+
*
|
|
8
|
+
* A shortcut is a string: steps separated by spaces, each step modifiers and
|
|
9
|
+
* a key joined by `+`. `mod+k` is ⌘K on Apple platforms and Ctrl+K
|
|
10
|
+
* elsewhere; `shift+?`, `alt+arrowup`, `escape`; `g i` is G then I. Keys are
|
|
11
|
+
* a character or a name (enter, escape, space, tab, backspace, delete, the
|
|
12
|
+
* arrows, home, end, pageup, pagedown, f1 to f12, plus).
|
|
13
|
+
*
|
|
14
|
+
* Who hears a key, in order:
|
|
15
|
+
* - Nothing does while an IME is composing, or once a component has handled
|
|
16
|
+
* the key itself (`event.preventDefault()`), so a list's own arrow keys or
|
|
17
|
+
* an input's own Escape never also fire a shortcut. Escape is the layer
|
|
18
|
+
* stack's while any overlay is open (useLayer), so an `escape` shortcut
|
|
19
|
+
* is for closing something that is not a library overlay.
|
|
20
|
+
* - Nothing does while focus is in a text field, unless the shortcut opts in
|
|
21
|
+
* with `allowInInput` (a palette's Cmd+K should work from a search box).
|
|
22
|
+
* - A `global` shortcut always does.
|
|
23
|
+
* - A `scope`d shortcut does while focus is inside its element, or while
|
|
24
|
+
* nothing has focus and the element sits on the surface in front (the open
|
|
25
|
+
* modal, else the page). Lightbox's arrows are scoped to its panel.
|
|
26
|
+
* - Any other shortcut belongs to the page: it does only while no modal is
|
|
27
|
+
* open and focus is not inside an open overlay (a menu, a popover), the
|
|
28
|
+
* same topmost-only rule the layer stack applies to Escape.
|
|
29
|
+
* When several match, the longest sequence wins, then the shortcut
|
|
30
|
+
* registered last. A matched key's default is prevented.
|
|
31
|
+
*
|
|
32
|
+
* Exactly one document listener exists while any shortcut is registered, and
|
|
33
|
+
* none otherwise. scripts/validate-overlay-listeners.mjs holds every other
|
|
34
|
+
* document key listener in the library and the site to this module.
|
|
35
|
+
*/
|
|
36
|
+
export interface ShortcutOptions {
|
|
37
|
+
/** What the shortcut does, in a few words, for a help list built from `useRegisteredShortcuts`. */
|
|
38
|
+
description?: string;
|
|
39
|
+
/** The heading a help list files the shortcut under, such as "Navigation". */
|
|
40
|
+
group?: string;
|
|
41
|
+
/** Whether the shortcut is live. Default true. */
|
|
42
|
+
enabled?: boolean;
|
|
43
|
+
/** Fires only while focus is inside this element (see the module's rules). */
|
|
44
|
+
scope?: React.RefObject<HTMLElement | null>;
|
|
45
|
+
/** Fires whatever is open or focused, outside text fields. Default false. */
|
|
46
|
+
global?: boolean;
|
|
47
|
+
/** Fires while focus is in a text field too. Default false. */
|
|
48
|
+
allowInInput?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The platform shortcuts are drawn for. The server and the first client
|
|
52
|
+
* render both say `other`, then the client settles on its own, so drawing a
|
|
53
|
+
* shortcut never breaks hydration.
|
|
54
|
+
*/
|
|
55
|
+
export declare function usePlatform(): ShortcutPlatform;
|
|
56
|
+
/**
|
|
57
|
+
* Binds a shortcut (or several, as an array) to `handler` while the component
|
|
58
|
+
* is mounted and `enabled` is not false. The handler and options are read at
|
|
59
|
+
* key time, so passing new ones each render never re-registers the shortcut.
|
|
60
|
+
*/
|
|
61
|
+
export declare function useShortcut(shortcut: Shortcut | Shortcut[], handler: (event: KeyboardEvent) => void, options?: ShortcutOptions): void;
|
|
62
|
+
/** A live shortcut as a help list reads it. */
|
|
63
|
+
export interface RegisteredShortcut {
|
|
64
|
+
/** The shortcuts that run it, in the grammar. */
|
|
65
|
+
shortcuts: Shortcut[];
|
|
66
|
+
/** What it does, from the binding's `description`. */
|
|
67
|
+
description: string;
|
|
68
|
+
/** Its heading, from the binding's `group`. */
|
|
69
|
+
group?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Every live shortcut that has a description, in registration order, for a
|
|
73
|
+
* "keyboard shortcuts" help dialog. Updates as shortcuts mount and unmount.
|
|
74
|
+
*/
|
|
75
|
+
export declare function useRegisteredShortcuts(): RegisteredShortcut[];
|