@seekalfred/primitives 0.6.0 β 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -11
- package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
- package/dist/activity-heatmap/activity-heatmap.js +699 -0
- package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
- package/dist/activity-heatmap/index.d.ts +2 -0
- package/dist/activity-heatmap/index.js +2 -0
- package/dist/alert/alert.d.ts +5 -1
- package/dist/alert/alert.js +68 -7
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert-dialog/alert-dialog.d.ts +45 -18
- package/dist/alert-dialog/alert-dialog.js +128 -9
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/alert-dialog/index.d.ts +2 -2
- package/dist/alert-dialog/index.js +2 -2
- package/dist/animated-counter/animated-counter.d.ts +38 -0
- package/dist/animated-counter/animated-counter.js +230 -0
- package/dist/animated-counter/animated-counter.js.map +1 -0
- package/dist/animated-counter/index.d.ts +2 -0
- package/dist/animated-counter/index.js +2 -0
- package/dist/announcement-bar/announcement-bar.d.ts +67 -0
- package/dist/announcement-bar/announcement-bar.js +364 -0
- package/dist/announcement-bar/announcement-bar.js.map +1 -0
- package/dist/announcement-bar/index.d.ts +2 -0
- package/dist/announcement-bar/index.js +2 -0
- package/dist/badge/badge.d.ts +1 -1
- package/dist/bar-chart/bar-chart.d.ts +51 -0
- package/dist/bar-chart/bar-chart.js +565 -0
- package/dist/bar-chart/bar-chart.js.map +1 -0
- package/dist/bar-chart/index.d.ts +2 -0
- package/dist/bar-chart/index.js +2 -0
- package/dist/breadcrumb/breadcrumb.d.ts +2 -2
- package/dist/breadcrumb/breadcrumb.js +12 -3
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/brush-chart/brush-chart.d.ts +64 -0
- package/dist/brush-chart/brush-chart.js +1056 -0
- package/dist/brush-chart/brush-chart.js.map +1 -0
- package/dist/brush-chart/index.d.ts +2 -0
- package/dist/brush-chart/index.js +2 -0
- package/dist/button/button.d.ts +2 -2
- package/dist/button/button.js +31 -21
- package/dist/button/button.js.map +1 -1
- package/dist/button-group/button-group.js +3 -4
- package/dist/button-group/button-group.js.map +1 -1
- package/dist/card/card.js +1 -1
- package/dist/card/card.js.map +1 -1
- package/dist/card-stack/card-stack.d.ts +54 -0
- package/dist/card-stack/card-stack.js +492 -0
- package/dist/card-stack/card-stack.js.map +1 -0
- package/dist/card-stack/index.d.ts +2 -0
- package/dist/card-stack/index.js +2 -0
- package/dist/carousel/carousel.d.ts +56 -0
- package/dist/carousel/carousel.js +389 -0
- package/dist/carousel/carousel.js.map +1 -0
- package/dist/carousel/index.d.ts +2 -0
- package/dist/carousel/index.js +2 -0
- package/dist/checkbox/checkbox.js +4 -4
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/chip-group/chip-group.d.ts +37 -0
- package/dist/chip-group/chip-group.js +148 -0
- package/dist/chip-group/chip-group.js.map +1 -0
- package/dist/chip-group/index.d.ts +2 -0
- package/dist/chip-group/index.js +2 -0
- package/dist/countdown/countdown.d.ts +45 -0
- package/dist/countdown/countdown.js +298 -0
- package/dist/countdown/countdown.js.map +1 -0
- package/dist/countdown/index.d.ts +2 -0
- package/dist/countdown/index.js +2 -0
- package/dist/date-picker/picker-parts.js +1 -1
- package/dist/date-picker/picker-parts.js.map +1 -1
- package/dist/dialog/dialog.js +1 -1
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/donut-chart/donut-chart.d.ts +64 -0
- package/dist/donut-chart/donut-chart.js +576 -0
- package/dist/donut-chart/donut-chart.js.map +1 -0
- package/dist/donut-chart/index.d.ts +2 -0
- package/dist/donut-chart/index.js +2 -0
- package/dist/empty-state/empty-state.d.ts +21 -10
- package/dist/empty-state/empty-state.js +32 -26
- package/dist/empty-state/empty-state.js.map +1 -1
- package/dist/empty-state/index.d.ts +2 -2
- package/dist/empty-state/index.js +2 -2
- package/dist/file-drop-zone/file-drop-zone.js +1 -1
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
- package/dist/filter-bar/filter-bar.d.ts +47 -26
- package/dist/filter-bar/filter-bar.js +272 -156
- package/dist/filter-bar/filter-bar.js.map +1 -1
- package/dist/gauge/gauge.d.ts +45 -0
- package/dist/gauge/gauge.js +219 -0
- package/dist/gauge/gauge.js.map +1 -0
- package/dist/gauge/index.d.ts +2 -0
- package/dist/gauge/index.js +2 -0
- package/dist/in-view-title/in-view-title.d.ts +33 -0
- package/dist/in-view-title/in-view-title.js +119 -0
- package/dist/in-view-title/in-view-title.js.map +1 -0
- package/dist/in-view-title/index.d.ts +2 -0
- package/dist/in-view-title/index.js +2 -0
- package/dist/index.d.ts +49 -6
- package/dist/index.js +48 -8
- package/dist/lib/cn.js +11 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/field-message.js +28 -10
- package/dist/lib/field-message.js.map +1 -1
- package/dist/lib/field-surface.js +9 -8
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/flag-emoji.js +50 -0
- package/dist/lib/flag-emoji.js.map +1 -0
- package/dist/lib/flip.js +11 -3
- package/dist/lib/flip.js.map +1 -1
- package/dist/lib/menu-highlight.js +64 -0
- package/dist/lib/menu-highlight.js.map +1 -0
- package/dist/lib/motion.js +9 -2
- package/dist/lib/motion.js.map +1 -1
- package/dist/lib/rolling-value.js +1 -1
- package/dist/lib/rolling-value.js.map +1 -1
- package/dist/lib/spring-tween.js +500 -0
- package/dist/lib/spring-tween.js.map +1 -0
- package/dist/lib/swap-text.js +3 -1
- package/dist/lib/swap-text.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -0
- package/dist/line-chart/index.js +2 -0
- package/dist/line-chart/line-chart.d.ts +76 -0
- package/dist/line-chart/line-chart.js +611 -0
- package/dist/line-chart/line-chart.js.map +1 -0
- package/dist/mention-input/index.d.ts +2 -0
- package/dist/mention-input/index.js +2 -0
- package/dist/mention-input/mention-input.d.ts +98 -0
- package/dist/mention-input/mention-input.js +576 -0
- package/dist/mention-input/mention-input.js.map +1 -0
- package/dist/menu/menu.d.ts +2 -0
- package/dist/menu/menu.js +23 -12
- package/dist/menu/menu.js.map +1 -1
- package/dist/money-input/index.d.ts +2 -0
- package/dist/money-input/index.js +2 -0
- package/dist/money-input/money-input.d.ts +65 -0
- package/dist/money-input/money-input.js +607 -0
- package/dist/money-input/money-input.js.map +1 -0
- package/dist/morph-select/index.d.ts +2 -0
- package/dist/morph-select/index.js +2 -0
- package/dist/morph-select/morph-select.d.ts +71 -0
- package/dist/morph-select/morph-select.js +693 -0
- package/dist/morph-select/morph-select.js.map +1 -0
- package/dist/multi-select/index.d.ts +2 -0
- package/dist/multi-select/index.js +2 -0
- package/dist/multi-select/multi-select.d.ts +36 -0
- package/dist/multi-select/multi-select.js +152 -0
- package/dist/multi-select/multi-select.js.map +1 -0
- package/dist/number-field/index.d.ts +2 -0
- package/dist/number-field/index.js +2 -0
- package/dist/number-field/number-field.d.ts +42 -0
- package/dist/number-field/number-field.js +199 -0
- package/dist/number-field/number-field.js.map +1 -0
- package/dist/onboarding-checklist/index.d.ts +2 -0
- package/dist/onboarding-checklist/index.js +2 -0
- package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
- package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
- package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
- package/dist/password-field/password-field.js +2 -2
- package/dist/password-field/password-field.js.map +1 -1
- package/dist/phone-input/index.d.ts +3 -0
- package/dist/phone-input/index.js +3 -0
- package/dist/phone-input/phone-countries.d.ts +29 -0
- package/dist/phone-input/phone-countries.js +170 -0
- package/dist/phone-input/phone-countries.js.map +1 -0
- package/dist/phone-input/phone-input.d.ts +66 -0
- package/dist/phone-input/phone-input.js +440 -0
- package/dist/phone-input/phone-input.js.map +1 -0
- package/dist/popover/popover.d.ts +12 -0
- package/dist/popover/popover.js +31 -7
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.js +1 -1
- package/dist/radio-group/radio-group.js +1 -1
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/reactions/index.d.ts +2 -0
- package/dist/reactions/index.js +2 -0
- package/dist/reactions/reactions.d.ts +50 -0
- package/dist/reactions/reactions.js +315 -0
- package/dist/reactions/reactions.js.map +1 -0
- package/dist/rich-text-editor/index.d.ts +3 -0
- package/dist/rich-text-editor/index.js +2 -0
- package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
- package/dist/rich-text-editor/rich-text-editor.js +909 -0
- package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
- package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
- package/dist/rich-text-editor/rich-text-engine.js +366 -0
- package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
- package/dist/rich-text-editor/rich-text-html.js +174 -0
- package/dist/rich-text-editor/rich-text-html.js.map +1 -0
- package/dist/rich-text-editor/rich-text-icons.js +43 -0
- package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
- package/dist/ridgeline/index.d.ts +2 -0
- package/dist/ridgeline/index.js +2 -0
- package/dist/ridgeline/ridgeline.d.ts +55 -0
- package/dist/ridgeline/ridgeline.js +701 -0
- package/dist/ridgeline/ridgeline.js.map +1 -0
- package/dist/scroll-area/index.d.ts +2 -2
- package/dist/scroll-area/index.js +2 -2
- package/dist/scroll-area/scroll-area.d.ts +68 -31
- package/dist/scroll-area/scroll-area.js +208 -59
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/scrub-input/evaluate.d.ts +14 -0
- package/dist/scrub-input/evaluate.js +72 -0
- package/dist/scrub-input/evaluate.js.map +1 -0
- package/dist/scrub-input/index.d.ts +3 -0
- package/dist/scrub-input/index.js +3 -0
- package/dist/scrub-input/scrub-input.d.ts +74 -0
- package/dist/scrub-input/scrub-input.js +486 -0
- package/dist/scrub-input/scrub-input.js.map +1 -0
- package/dist/search-field/search-field.d.ts +2 -1
- package/dist/search-field/search-field.js +4 -4
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +20 -8
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +1 -1
- package/dist/sheet/sheet.js +2 -1
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/shortcut-recorder/index.d.ts +3 -0
- package/dist/shortcut-recorder/index.js +3 -0
- package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
- package/dist/shortcut-recorder/shortcut-model.js +206 -0
- package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
- package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
- package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
- package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
- package/dist/slider/index.d.ts +2 -2
- package/dist/slider/slider.d.ts +46 -14
- package/dist/slider/slider.js +151 -67
- package/dist/slider/slider.js.map +1 -1
- package/dist/slope-chart/index.d.ts +2 -0
- package/dist/slope-chart/index.js +2 -0
- package/dist/slope-chart/slope-chart.d.ts +54 -0
- package/dist/slope-chart/slope-chart.js +568 -0
- package/dist/slope-chart/slope-chart.js.map +1 -0
- package/dist/sparkline/index.d.ts +2 -0
- package/dist/sparkline/index.js +2 -0
- package/dist/sparkline/sparkline.d.ts +46 -0
- package/dist/sparkline/sparkline.js +390 -0
- package/dist/sparkline/sparkline.js.map +1 -0
- package/dist/split-bar/index.d.ts +2 -0
- package/dist/split-bar/index.js +2 -0
- package/dist/split-bar/split-bar.d.ts +62 -0
- package/dist/split-bar/split-bar.js +513 -0
- package/dist/split-bar/split-bar.js.map +1 -0
- package/dist/split-button/split-button.d.ts +1 -1
- package/dist/split-button/split-button.js +12 -11
- package/dist/split-button/split-button.js.map +1 -1
- package/dist/stat-card/index.d.ts +2 -2
- package/dist/stat-card/index.js +2 -2
- package/dist/stat-card/stat-card.d.ts +24 -4
- package/dist/stat-card/stat-card.js +114 -17
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/stepper/index.d.ts +2 -0
- package/dist/stepper/index.js +2 -0
- package/dist/stepper/stepper.d.ts +51 -0
- package/dist/stepper/stepper.js +215 -0
- package/dist/stepper/stepper.js.map +1 -0
- package/dist/streamgraph/index.d.ts +2 -0
- package/dist/streamgraph/index.js +2 -0
- package/dist/streamgraph/streamgraph.d.ts +70 -0
- package/dist/streamgraph/streamgraph.js +613 -0
- package/dist/streamgraph/streamgraph.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/swipe-actions/index.d.ts +2 -0
- package/dist/swipe-actions/index.js +2 -0
- package/dist/swipe-actions/swipe-actions.d.ts +67 -0
- package/dist/swipe-actions/swipe-actions.js +552 -0
- package/dist/swipe-actions/swipe-actions.js.map +1 -0
- package/dist/switch/switch.js +2 -2
- package/dist/switch/switch.js.map +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +56 -4
- package/dist/table/table.js +340 -165
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.js +2 -2
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/tag-input/index.d.ts +2 -0
- package/dist/tag-input/index.js +2 -0
- package/dist/tag-input/tag-input.d.ts +40 -0
- package/dist/tag-input/tag-input.js +269 -0
- package/dist/tag-input/tag-input.js.map +1 -0
- package/dist/text-morph/index.d.ts +2 -0
- package/dist/text-morph/index.js +2 -0
- package/dist/text-morph/text-morph.d.ts +23 -0
- package/dist/text-morph/text-morph.js +21 -0
- package/dist/text-morph/text-morph.js.map +1 -0
- package/dist/text-reveal/index.d.ts +2 -0
- package/dist/text-reveal/index.js +2 -0
- package/dist/text-reveal/text-reveal.d.ts +27 -0
- package/dist/text-reveal/text-reveal.js +51 -0
- package/dist/text-reveal/text-reveal.js.map +1 -0
- package/dist/text-shimmer/index.d.ts +2 -0
- package/dist/text-shimmer/index.js +2 -0
- package/dist/text-shimmer/text-shimmer.d.ts +29 -0
- package/dist/text-shimmer/text-shimmer.js +29 -0
- package/dist/text-shimmer/text-shimmer.js.map +1 -0
- package/dist/time-picker/time-picker.js +2 -2
- package/dist/time-picker/time-picker.js.map +1 -1
- package/dist/toast/index.d.ts +2 -2
- package/dist/toast/index.js +2 -2
- package/dist/toast/toast.d.ts +6 -0
- package/dist/toast/toast.js +82 -40
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +9 -2
- package/dist/tooltip/tooltip.js +7 -7
- package/dist/tooltip/tooltip.js.map +1 -1
- package/dist/treemap/index.d.ts +2 -0
- package/dist/treemap/index.js +2 -0
- package/dist/treemap/treemap.d.ts +53 -0
- package/dist/treemap/treemap.js +699 -0
- package/dist/treemap/treemap.js.map +1 -0
- package/dist/usage-meter/index.d.ts +2 -0
- package/dist/usage-meter/index.js +2 -0
- package/dist/usage-meter/usage-meter.d.ts +50 -0
- package/dist/usage-meter/usage-meter.js +440 -0
- package/dist/usage-meter/usage-meter.js.map +1 -0
- package/dist/waffle-chart/index.d.ts +2 -0
- package/dist/waffle-chart/index.js +2 -0
- package/dist/waffle-chart/waffle-chart.d.ts +57 -0
- package/dist/waffle-chart/waffle-chart.js +426 -0
- package/dist/waffle-chart/waffle-chart.js.map +1 -0
- package/package.json +165 -3
package/dist/index.js
CHANGED
|
@@ -2,53 +2,93 @@ import { cn, cnAll } from "./lib/cn.js";
|
|
|
2
2
|
import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger } from "./accordion/accordion.js";
|
|
3
3
|
import { Button, buttonVariants } from "./button/button.js";
|
|
4
4
|
import { ActionButton } from "./action-button/action-button.js";
|
|
5
|
+
import { Tab, TabList, TabPanel, Tabs, tabListVariants, tabPanelVariants, tabVariants, tabsVariants } from "./tabs/tabs.js";
|
|
6
|
+
import { ActivityHeatmap } from "./activity-heatmap/activity-heatmap.js";
|
|
5
7
|
import { Dialog, DialogFooter, DialogTrigger, dialogDescriptionVariants, dialogFooterVariants, dialogOverlayVariants, dialogPanelVariants, dialogTitleVariants } from "./dialog/dialog.js";
|
|
6
|
-
import { AlertDialog } from "./alert-dialog/alert-dialog.js";
|
|
8
|
+
import { AlertDialog, AlertDialogAction, AlertDialogCancel } from "./alert-dialog/alert-dialog.js";
|
|
9
|
+
import { AnimatedCounter } from "./animated-counter/animated-counter.js";
|
|
7
10
|
import { Alert, alertVariants } from "./alert/alert.js";
|
|
11
|
+
import { AnnouncementBar, clearAnnouncementDismissal } from "./announcement-bar/announcement-bar.js";
|
|
8
12
|
import { Avatar, avatarVariants } from "./avatar/avatar.js";
|
|
9
13
|
import { Badge, badgeVariants } from "./badge/badge.js";
|
|
14
|
+
import { BarChart } from "./bar-chart/bar-chart.js";
|
|
10
15
|
import { BreadcrumbItem, Breadcrumbs, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants } from "./breadcrumb/breadcrumb.js";
|
|
16
|
+
import { BrushChart } from "./brush-chart/brush-chart.js";
|
|
11
17
|
import { Menu, MenuItem, MenuSeparator, MenuTrigger, menuItemVariants, menuPopoverVariants, menuSeparatorVariants, menuVariants } from "./menu/menu.js";
|
|
12
18
|
import { ButtonGroup } from "./button-group/button-group.js";
|
|
13
19
|
import { Calendar, CalendarRange, calendarCellVariants, calendarClearVariants, calendarGridVariants, calendarHeaderCellVariants, calendarHeaderVariants, calendarHeadingVariants, calendarLabelVariants, calendarMonthsVariants, calendarNavVariants, calendarVariants } from "./calendar/calendar.js";
|
|
14
20
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, cardContentVariants, cardDescriptionVariants, cardFooterVariants, cardHeaderVariants, cardTitleVariants, cardVariants } from "./card/card.js";
|
|
21
|
+
import { CardStack } from "./card-stack/card-stack.js";
|
|
22
|
+
import { Carousel } from "./carousel/carousel.js";
|
|
15
23
|
import { Checkbox, checkboxBoxVariants, checkboxVariants } from "./checkbox/checkbox.js";
|
|
24
|
+
import { ChipGroup } from "./chip-group/chip-group.js";
|
|
16
25
|
import { ConfirmMorph } from "./confirm-morph/confirm-morph.js";
|
|
17
26
|
import { CopyButton } from "./copy-button/copy-button.js";
|
|
27
|
+
import { Countdown } from "./countdown/countdown.js";
|
|
18
28
|
import { Popover, PopoverTrigger, popoverVariants } from "./popover/popover.js";
|
|
19
29
|
import { datePickerInputVariants, datePickerLabelVariants, datePickerSegmentVariants, datePickerTriggerVariants, datePickerVariants } from "./date-picker/picker-parts.js";
|
|
20
30
|
import { DateRangePicker, defaultDateRangePresets } from "./date-picker/date-range-picker.js";
|
|
21
31
|
import { DatePicker } from "./date-picker/date-picker.js";
|
|
32
|
+
import { DonutChart } from "./donut-chart/donut-chart.js";
|
|
22
33
|
import { EmptyState, emptyStateActionVariants, emptyStateTextVariants, emptyStateVariants } from "./empty-state/empty-state.js";
|
|
23
34
|
import { ExpandingButtonGroup } from "./expanding-button-group/expanding-button-group.js";
|
|
24
35
|
import { FileDropZone, fileDropZoneHintVariants, fileDropZonePromptVariants, fileDropZoneVariants } from "./file-drop-zone/file-drop-zone.js";
|
|
25
|
-
import { Label, labelVariants } from "./label/label.js";
|
|
26
|
-
import { Select, SelectItem, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants } from "./select/select.js";
|
|
27
36
|
import { FilterBar, filterBarChipVariants, filterBarRowVariants, filterBarVariants } from "./filter-bar/filter-bar.js";
|
|
28
37
|
import { Tooltip, TooltipTrigger, tooltipVariants } from "./tooltip/tooltip.js";
|
|
29
38
|
import { FloatingButtonGroup } from "./floating-button-group/floating-button-group.js";
|
|
39
|
+
import { Gauge } from "./gauge/gauge.js";
|
|
30
40
|
import { BellIcon, ChevronIcon, CopyCheckIcon, HeartIcon, IconMorph, MenuIcon, PlayPauseIcon, PlusCheckIcon, SunMoonIcon } from "./icon-morph/icon-morph.js";
|
|
41
|
+
import { InViewTitle } from "./in-view-title/in-view-title.js";
|
|
42
|
+
import { Label, labelVariants } from "./label/label.js";
|
|
31
43
|
import { Input, inputVariants } from "./input/input.js";
|
|
44
|
+
import { LINE_CHART_DRAW_MS, LineChart } from "./line-chart/line-chart.js";
|
|
45
|
+
import { MentionInput, mentionText, serializeMentions } from "./mention-input/mention-input.js";
|
|
46
|
+
import { Select, SelectItem, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants } from "./select/select.js";
|
|
47
|
+
import { MoneyInput } from "./money-input/money-input.js";
|
|
48
|
+
import { MorphSelect } from "./morph-select/morph-select.js";
|
|
49
|
+
import { MultiSelect } from "./multi-select/multi-select.js";
|
|
50
|
+
import { NumberField } from "./number-field/number-field.js";
|
|
51
|
+
import { OnboardingChecklist } from "./onboarding-checklist/onboarding-checklist.js";
|
|
32
52
|
import { PageHeader, pageHeaderActionsVariants, pageHeaderBackVariants, pageHeaderDescriptionVariants, pageHeaderMetaVariants, pageHeaderRowVariants, pageHeaderTitleVariants, pageHeaderVariants } from "./page-header/page-header.js";
|
|
33
53
|
import { PasswordField, defaultPasswordFieldMessages } from "./password-field/password-field.js";
|
|
34
54
|
import { PasswordStrength, countCodePoints, createPasswordRules, defaultPasswordRules, defaultPasswordStrengthMessages, estimateStrength } from "./password-strength/password-strength.js";
|
|
55
|
+
import { ScrollArea, scrollAreaRootVariants, scrollAreaVariants } from "./scroll-area/scroll-area.js";
|
|
56
|
+
import { PHONE_COUNTRIES, formatNational, formatPhoneNumber, parsePhoneNumber } from "./phone-input/phone-countries.js";
|
|
57
|
+
import { PhoneInput } from "./phone-input/phone-input.js";
|
|
35
58
|
import { Progress, progressFillVariants, progressTrackVariants, progressVariants } from "./progress/progress.js";
|
|
36
59
|
import { Radio, RadioGroup, radioGroupVariants, radioVariants } from "./radio-group/radio-group.js";
|
|
37
|
-
import {
|
|
60
|
+
import { Reactions } from "./reactions/reactions.js";
|
|
61
|
+
import { Ridgeline } from "./ridgeline/ridgeline.js";
|
|
62
|
+
import { evaluateScrubExpression } from "./scrub-input/evaluate.js";
|
|
63
|
+
import { ScrubInput } from "./scrub-input/scrub-input.js";
|
|
38
64
|
import { SearchField } from "./search-field/search-field.js";
|
|
39
65
|
import { Separator, separatorVariants } from "./separator/separator.js";
|
|
40
66
|
import { Sheet, sheetOverlayVariants, sheetPanelVariants } from "./sheet/sheet.js";
|
|
67
|
+
import { formatShortcut, matchesShortcut, normalizeShortcut, shortcutFromEvent, shortcutTokens } from "./shortcut-recorder/shortcut-model.js";
|
|
68
|
+
import { ShortcutKbd, ShortcutKeys, ShortcutRecorder, usePlatform, usePressedKeys } from "./shortcut-recorder/shortcut-recorder.js";
|
|
41
69
|
import { SideNav, sideNavHeaderVariants, sideNavItemVariants, sideNavMenuVariants, sideNavVariants } from "./side-nav/side-nav.js";
|
|
42
70
|
import { Skeleton, skeletonVariants } from "./skeleton/skeleton.js";
|
|
43
71
|
import { Slider, sliderBarVariants, sliderFillVariants, sliderThumbVariants, sliderTrackVariants, sliderVariants } from "./slider/slider.js";
|
|
72
|
+
import { SlopeChart } from "./slope-chart/slope-chart.js";
|
|
44
73
|
import { Spinner, spinnerVariants } from "./spinner/spinner.js";
|
|
74
|
+
import { SplitBar } from "./split-bar/split-bar.js";
|
|
45
75
|
import { SplitButton } from "./split-button/split-button.js";
|
|
46
|
-
import {
|
|
76
|
+
import { SPARKLINE_DRAW_MS, Sparkline, sparklineVariants } from "./sparkline/sparkline.js";
|
|
77
|
+
import { StatCard, statCardHeaderVariants, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants } from "./stat-card/stat-card.js";
|
|
78
|
+
import { Stepper } from "./stepper/stepper.js";
|
|
79
|
+
import { Streamgraph } from "./streamgraph/streamgraph.js";
|
|
80
|
+
import { SwipeActions, SwipeActionsRow } from "./swipe-actions/swipe-actions.js";
|
|
47
81
|
import { Switch, switchThumbVariants, switchTrackVariants } from "./switch/switch.js";
|
|
48
|
-
import { Table, createColumns, tableCardVariants, tableCellVariants, tableColumnVariants, tableFooterVariants, tableHeaderVariants, tablePaginationVariants, tableRowVariants, tableScrollVariants, tableVariants } from "./table/table.js";
|
|
49
|
-
import {
|
|
82
|
+
import { Table, createColumns, tableCardVariants, tableCellVariants, tableColumnVariants, tableEmptyVariants, tableFooterVariants, tableHeaderVariants, tablePaginationVariants, tableRowVariants, tableScrollVariants, tableSelectionBarVariants, tableVariants } from "./table/table.js";
|
|
83
|
+
import { TextShimmer } from "./text-shimmer/text-shimmer.js";
|
|
84
|
+
import { TagInput } from "./tag-input/tag-input.js";
|
|
85
|
+
import { TextMorph } from "./text-morph/text-morph.js";
|
|
86
|
+
import { TextReveal } from "./text-reveal/text-reveal.js";
|
|
50
87
|
import { Textarea, textareaVariants } from "./textarea/textarea.js";
|
|
51
88
|
import { ThemeSwitch } from "./theme-switch/theme-switch.js";
|
|
52
89
|
import { TimePicker, timePickerItemVariants, timePickerListVariants } from "./time-picker/time-picker.js";
|
|
53
90
|
import { ToastRegion, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants } from "./toast/toast.js";
|
|
54
|
-
|
|
91
|
+
import { Treemap } from "./treemap/treemap.js";
|
|
92
|
+
import { UsageMeter } from "./usage-meter/usage-meter.js";
|
|
93
|
+
import { WaffleChart } from "./waffle-chart/waffle-chart.js";
|
|
94
|
+
export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, ActionButton, ActivityHeatmap, Alert, AlertDialog, AlertDialogAction, AlertDialogCancel, AnimatedCounter, AnnouncementBar, Avatar, Badge, BarChart, BellIcon, BreadcrumbItem, Breadcrumbs, BrushChart, Button, ButtonGroup, Calendar, CalendarRange, Card, CardContent, CardDescription, CardFooter, CardHeader, CardStack, CardTitle, Carousel, Checkbox, ChevronIcon, ChipGroup, ConfirmMorph, CopyButton, CopyCheckIcon, Countdown, DatePicker, DateRangePicker, Dialog, DialogFooter, DialogTrigger, DonutChart, EmptyState, ExpandingButtonGroup, FileDropZone, FilterBar, FloatingButtonGroup, Gauge, HeartIcon, IconMorph, InViewTitle, Input, LINE_CHART_DRAW_MS, Label, LineChart, MentionInput, Menu, MenuIcon, MenuItem, MenuSeparator, MenuTrigger, MoneyInput, MorphSelect, MultiSelect, NumberField, OnboardingChecklist, PHONE_COUNTRIES, PageHeader, PasswordField, PasswordStrength, PhoneInput, PlayPauseIcon, PlusCheckIcon, Popover, PopoverTrigger, Progress, Radio, RadioGroup, Reactions, Ridgeline, SPARKLINE_DRAW_MS, ScrollArea, ScrubInput, SearchField, Select, SelectItem, Separator, Sheet, ShortcutKbd, ShortcutKeys, ShortcutRecorder, SideNav, Skeleton, Slider, SlopeChart, Sparkline, Spinner, SplitBar, SplitButton, StatCard, Stepper, Streamgraph, SunMoonIcon, SwipeActions, SwipeActionsRow, Switch, Tab, TabList, TabPanel, Table, Tabs, TagInput, TextMorph, TextReveal, TextShimmer, Textarea, ThemeSwitch, TimePicker, ToastRegion, Tooltip, TooltipTrigger, Treemap, UsageMeter, WaffleChart, alertVariants, avatarVariants, badgeVariants, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants, buttonVariants, calendarCellVariants, calendarClearVariants, calendarGridVariants, calendarHeaderCellVariants, calendarHeaderVariants, calendarHeadingVariants, calendarLabelVariants, calendarMonthsVariants, calendarNavVariants, calendarVariants, cardContentVariants, cardDescriptionVariants, cardFooterVariants, cardHeaderVariants, cardTitleVariants, cardVariants, checkboxBoxVariants, checkboxVariants, clearAnnouncementDismissal, cn, cnAll, countCodePoints, createColumns, createPasswordRules, datePickerInputVariants, datePickerLabelVariants, datePickerSegmentVariants, datePickerTriggerVariants, datePickerVariants, defaultDateRangePresets, defaultPasswordFieldMessages, defaultPasswordRules, defaultPasswordStrengthMessages, dialogDescriptionVariants, dialogFooterVariants, dialogOverlayVariants, dialogPanelVariants, dialogTitleVariants, emptyStateActionVariants, emptyStateTextVariants, emptyStateVariants, estimateStrength, evaluateScrubExpression, fileDropZoneHintVariants, fileDropZonePromptVariants, fileDropZoneVariants, filterBarChipVariants, filterBarRowVariants, filterBarVariants, formatNational, formatPhoneNumber, formatShortcut, inputVariants, labelVariants, matchesShortcut, mentionText, menuItemVariants, menuPopoverVariants, menuSeparatorVariants, menuVariants, normalizeShortcut, pageHeaderActionsVariants, pageHeaderBackVariants, pageHeaderDescriptionVariants, pageHeaderMetaVariants, pageHeaderRowVariants, pageHeaderTitleVariants, pageHeaderVariants, parsePhoneNumber, popoverVariants, progressFillVariants, progressTrackVariants, progressVariants, radioGroupVariants, radioVariants, scrollAreaRootVariants, scrollAreaVariants, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants, separatorVariants, serializeMentions, sheetOverlayVariants, sheetPanelVariants, shortcutFromEvent, shortcutTokens, sideNavHeaderVariants, sideNavItemVariants, sideNavMenuVariants, sideNavVariants, skeletonVariants, sliderBarVariants, sliderFillVariants, sliderThumbVariants, sliderTrackVariants, sliderVariants, sparklineVariants, spinnerVariants, statCardHeaderVariants, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants, switchThumbVariants, switchTrackVariants, tabListVariants, tabPanelVariants, tabVariants, tableCardVariants, tableCellVariants, tableColumnVariants, tableEmptyVariants, tableFooterVariants, tableHeaderVariants, tablePaginationVariants, tableRowVariants, tableScrollVariants, tableSelectionBarVariants, tableVariants, tabsVariants, textareaVariants, timePickerItemVariants, timePickerListVariants, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants, tooltipVariants, usePlatform, usePressedKeys };
|
package/dist/lib/cn.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { clsx } from "clsx";
|
|
2
|
-
import {
|
|
2
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
3
3
|
//#region src/lib/cn.ts
|
|
4
4
|
/**
|
|
5
|
+
* tailwind-merge knows radius values only by T-shirt size (sm, md, xl...).
|
|
6
|
+
* `rounded-control` and `rounded-panel` are this system's own names, so without
|
|
7
|
+
* this a later `rounded-none` (Sheet, squaring the Dialog panel against the
|
|
8
|
+
* viewport edge) would not replace them and both would reach the element.
|
|
9
|
+
*/
|
|
10
|
+
const twMerge = extendTailwindMerge({ extend: { theme: {
|
|
11
|
+
radius: ["control", "panel"],
|
|
12
|
+
text: [(size) => /^(2xs|h[1-4]|display-\w+)$/.test(size)]
|
|
13
|
+
} } });
|
|
14
|
+
/**
|
|
5
15
|
* Merge OUR classes, then append the consumer's UNTOUCHED.
|
|
6
16
|
*
|
|
7
17
|
* The consumer's `className` deliberately does not go through tailwind-merge.
|
package/dist/lib/cn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cn.js","names":[],"sources":["../../src/lib/cn.ts"],"sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport {
|
|
1
|
+
{"version":3,"file":"cn.js","names":[],"sources":["../../src/lib/cn.ts"],"sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n/**\n * tailwind-merge knows radius values only by T-shirt size (sm, md, xl...).\n * `rounded-control` and `rounded-panel` are this system's own names, so without\n * this a later `rounded-none` (Sheet, squaring the Dialog panel against the\n * viewport edge) would not replace them and both would reach the element.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n theme: {\n radius: [\"control\", \"panel\"],\n // The type scale's own sizes (packages/tokens/tokens/scale.json \"text\").\n // tailwind-merge knows font sizes only by T-shirt name, so it took\n // `text-h2` for a text COLOUR and `cn(\"text-h2\", \"text-foreground\")`\n // dropped the size: the element rendered at the inherited 16px.\n text: [(size: string) => /^(2xs|h[1-4]|display-\\w+)$/.test(size)],\n },\n },\n});\n\n/**\n * Merge OUR classes, then append the consumer's UNTOUCHED.\n *\n * The consumer's `className` deliberately does not go through tailwind-merge.\n *\n * WHY. tailwind-merge resolves conflicts by class GROUP, and several of its\n * groups are matched by validators that accept any token -- `text-color` takes\n * anything shaped `text-<word>-<number>`, which is also the shape of a\n * consuming app's own utilities. It cannot tell `text-red-500` from a custom\n * `text-semibold-14`, so it treats them as conflicting and deletes one. There\n * is no error, no warning and no type error; the element simply renders without\n * the class the author wrote.\n *\n * Measured in the consuming app before this changed: 38 call sites passed a\n * custom type-scale class and 14 lost it. Two of those flipped at runtime\n * depending on a data value, which is why it went unnoticed for so long. A\n * second, unrelated utility (`bg-sidebar-bg`) was being eaten the same way, so\n * the defect is structural rather than specific to typography.\n *\n * The loss happens INSIDE the consumer's own string, not between theirs and\n * ours -- a lone consumer class survives, because it comes last and\n * tailwind-merge keeps the last of a group. It is a pair like\n * `\"text-semibold-14 text-white\"` that loses its first half. So no amount of\n * ordering fixes it: their string has to stay out of the merge entirely.\n *\n * WHY THAT IS SAFE. \"The consumer always wins\" is delivered twice over. This\n * package ships entirely inside `@layer alfred`, and unlayered CSS beats every\n * layered rule regardless of specificity (ARCHITECTURE.md section 5;\n * PHASE-0.md). `apps/smoke` asserts exactly that on every build. tailwind-merge\n * was belt-and-braces for something the cascade already handles -- and the\n * braces were cutting.\n *\n * WHAT THIS IS NOT. It is not \"stop merging\". A consumer class that\n * tailwind-merge genuinely understands still wins outright -- `max-w-96` over\n * our `max-w-128` collapses to one class, exactly as before. Only the tokens\n * the merge DISCARDED from their string come back, which is precisely the set\n * it had no business discarding.\n *\n * The one behaviour change to know about: if a consumer passes two conflicting\n * classes of their own (`className=\"p-2 p-4\"`), both now reach the element and\n * CSS picks by stylesheet order rather than by token order. Their string, their\n * conflict -- and the alternative is deleting custom utilities, which is worse.\n *\n * @param ours classes this library authored -- merged against each other\n * @param theirs the consumer's `className` -- merged, then anything the merge\n * discarded from it is restored\n */\nexport function cn(ours: ClassValue, theirs?: ClassValue): string {\n const mine = clsx(ours);\n const yours = clsx(theirs);\n if (!yours) return twMerge(mine);\n\n // Merge normally first, so a consumer class that tailwind-merge DOES\n // understand still beats ours -- `max-w-96` over our `max-w-128` resolves to\n // one class, as it always did.\n const merged = twMerge(`${mine} ${yours}`);\n\n // Then put back anything of theirs the merge ate. Only their tokens are\n // restored, never ours, so this cannot resurrect a class the consumer\n // deliberately overrode.\n const kept = new Set(merged.split(\" \"));\n const rescued = yours.split(\" \").filter((token) => token && !kept.has(token));\n\n return rescued.length === 0 ? merged : `${merged} ${rescued.join(\" \")}`;\n}\n\n/**\n * Merge classes that are ALL ours.\n *\n * The same tailwind-merge behaviour `cn` used to have, for the call sites that\n * combine several of this library's own class strings and never touch a\n * consumer's. Keeping them on a separate function is what lets `cn` treat its\n * second argument as foreign without guessing from argument position -- several\n * internal call sites legitimately end with a library-owned conditional.\n */\nexport function cnAll(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,UAAU,oBAAoB,EAClC,QAAQ,EACN,OAAO;CACL,QAAQ,CAAC,WAAW,OAAO;CAK3B,MAAM,EAAE,SAAiB,6BAA6B,KAAK,IAAI,CAAC;AAClE,EACF,EACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDD,SAAgB,GAAG,MAAkB,QAA6B;CAChE,MAAM,OAAO,KAAK,IAAI;CACtB,MAAM,QAAQ,KAAK,MAAM;CACzB,IAAI,CAAC,OAAO,OAAO,QAAQ,IAAI;CAK/B,MAAM,SAAS,QAAQ,GAAG,KAAK,GAAG,OAAO;CAKzC,MAAM,OAAO,IAAI,IAAI,OAAO,MAAM,GAAG,CAAC;CACtC,MAAM,UAAU,MAAM,MAAM,GAAG,CAAC,CAAC,QAAQ,UAAU,SAAS,CAAC,KAAK,IAAI,KAAK,CAAC;CAE5E,OAAO,QAAQ,WAAW,IAAI,SAAS,GAAG,OAAO,GAAG,QAAQ,KAAK,GAAG;AACtE;;;;;;;;;;AAWA,SAAgB,MAAM,GAAG,QAA8B;CACrD,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B"}
|
|
@@ -48,9 +48,26 @@ function LeavingDigit({ id, rect, onDone, children }) {
|
|
|
48
48
|
children
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
/** One digit, rolling in or not as it was when it mounted: a later change of `animate` must not replay it. */
|
|
52
|
+
function Digit({ nodeRef, animate, children }) {
|
|
53
|
+
const [rolls] = useState(animate);
|
|
54
|
+
return /* @__PURE__ */ jsx("span", {
|
|
55
|
+
ref: nodeRef,
|
|
56
|
+
className: rolls ? "motion-safe:animate-field-digit-in" : void 0,
|
|
57
|
+
children
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function DigitColumn({ column, direction, leaving, still, onDone }) {
|
|
52
61
|
const digitRef = useRef(null);
|
|
53
62
|
const digits = useLeavingItems(column.char, column.char, () => digitRef.current);
|
|
63
|
+
const [shownDigit, setShownDigit] = useState({
|
|
64
|
+
char: column.char,
|
|
65
|
+
changes: 0
|
|
66
|
+
});
|
|
67
|
+
if (shownDigit.char !== column.char) setShownDigit({
|
|
68
|
+
char: column.char,
|
|
69
|
+
changes: shownDigit.changes + 1
|
|
70
|
+
});
|
|
54
71
|
const done = useCallback(() => onDone(column.key), [onDone, column.key]);
|
|
55
72
|
useExitBackstop(leaving, done, SPRING_BACKSTOP);
|
|
56
73
|
useIsomorphicLayoutEffect(() => {
|
|
@@ -62,14 +79,14 @@ function DigitColumn({ column, direction, leaving, onDone }) {
|
|
|
62
79
|
if (leaving && event.target === event.currentTarget && event.animationName === "field-column-shrink") done();
|
|
63
80
|
},
|
|
64
81
|
className: cn("field-number-column", leaving ? "motion-safe:animate-field-column-out" : column.seq > 0 && "motion-safe:animate-field-column-in"),
|
|
65
|
-
children: [digits.leaving.map((item) => /* @__PURE__ */ jsx(LeavingDigit, {
|
|
82
|
+
children: [(still ? [] : digits.leaving).map((item) => /* @__PURE__ */ jsx(LeavingDigit, {
|
|
66
83
|
id: item.id,
|
|
67
84
|
rect: item.rect,
|
|
68
85
|
onDone: digits.remove,
|
|
69
86
|
children: item.value
|
|
70
|
-
}, item.id)), /* @__PURE__ */ jsx(
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
}, item.id)), /* @__PURE__ */ jsx(Digit, {
|
|
88
|
+
nodeRef: digitRef,
|
|
89
|
+
animate: shownDigit.changes > 0 && !still,
|
|
73
90
|
children: column.char
|
|
74
91
|
}, column.char)]
|
|
75
92
|
});
|
|
@@ -79,7 +96,7 @@ function DigitColumn({ column, direction, leaving, onDone }) {
|
|
|
79
96
|
* roll, and a gained or lost digit opens or closes its width. Digits arrive
|
|
80
97
|
* from below when the number grew and from above when it shrank.
|
|
81
98
|
*/
|
|
82
|
-
function RollingNumber({ word }) {
|
|
99
|
+
function RollingNumber({ word, direction: forced, still = false }) {
|
|
83
100
|
const [state, setState] = useState(() => ({
|
|
84
101
|
word,
|
|
85
102
|
direction: 1,
|
|
@@ -90,10 +107,10 @@ function RollingNumber({ word }) {
|
|
|
90
107
|
let { columns, gone, direction } = state;
|
|
91
108
|
if (state.word !== word) {
|
|
92
109
|
const seq = state.seq + 1;
|
|
93
|
-
direction = parseFloat(word.replace(/,/g, "")) < parseFloat(state.word.replace(/,/g, "")) ? -1 : 1;
|
|
94
|
-
columns = toColumns(word, seq, new Map(state.columns.map((column) => [column.key, column])));
|
|
110
|
+
direction = forced ?? (parseFloat(word.replace(/,/g, "")) < parseFloat(state.word.replace(/,/g, "")) ? -1 : 1);
|
|
111
|
+
columns = toColumns(word, still ? 0 : seq, new Map(state.columns.map((column) => [column.key, column])));
|
|
95
112
|
const kept = new Set(columns.map((column) => column.key));
|
|
96
|
-
gone = [...state.gone.filter((column) => !kept.has(column.key)), ...state.columns.filter((column) => !kept.has(column.key))];
|
|
113
|
+
gone = still ? [] : [...state.gone.filter((column) => !kept.has(column.key)), ...state.columns.filter((column) => !kept.has(column.key))];
|
|
97
114
|
setState({
|
|
98
115
|
word,
|
|
99
116
|
direction,
|
|
@@ -117,6 +134,7 @@ function RollingNumber({ word }) {
|
|
|
117
134
|
column,
|
|
118
135
|
direction,
|
|
119
136
|
leaving,
|
|
137
|
+
still,
|
|
120
138
|
onDone: finish
|
|
121
139
|
}, column.key)) });
|
|
122
140
|
}
|
|
@@ -331,6 +349,6 @@ function FieldMessage({ id, text, tone, wordExit = "settle", numbers = true, cop
|
|
|
331
349
|
});
|
|
332
350
|
}
|
|
333
351
|
//#endregion
|
|
334
|
-
export { FieldMessage };
|
|
352
|
+
export { FieldMessage, RollingNumber };
|
|
335
353
|
|
|
336
354
|
//# sourceMappingURL=field-message.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-message.js","names":[],"sources":["../../src/lib/field-message.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState, type CSSProperties } from \"react\";\n\nimport { cn } from \"./cn.js\";\nimport {\n prefersReducedMotion,\n useExitBackstop,\n useIsomorphicLayoutEffect,\n useLeavingItems,\n type Rect,\n} from \"./motion.js\";\nimport { SwapText } from \"./swap-text.js\";\n\n/*\n * The helper and error copy under a text field. Three layers of presence, each\n * a small piece of React because CSS cannot keep a node that React has already\n * unmounted: the row (opens and closes its height), the words (a changed word\n * rises in while the old one lifts away) and the numbers (one column per digit,\n * a gained or lost digit opens or closes its width, a changed digit rolls).\n *\n * Nothing animates on first mount at any layer: a row present at first render,\n * words in a row that has just opened, and digits in a column that has just\n * opened all appear as they are.\n */\n\nconst isNumber = (word: string) => /^\\d[\\d.,/:]*%?$/.test(word);\n\n/** How a replaced word leaves: Input's 180ms settle, Textarea's 160ms, or the sharper ease-exit curve Password field uses. */\nexport type WordExit = \"settle\" | \"quick\" | \"sharp\";\n\nconst WORD_OUT: Record<WordExit, string> = {\n settle: \"absolute inline-block whitespace-pre motion-safe:animate-field-word-out\",\n quick: \"absolute inline-block whitespace-pre motion-safe:animate-field-word-out-quick\",\n sharp: \"absolute inline-block whitespace-pre motion-safe:animate-field-word-out-sharp\",\n};\n\n/** The longest a spring exit takes (736ms) plus a margin, for the backstop that removes a node whose animation never ends. */\nconst SPRING_BACKSTOP = 900;\n\nconst rectOf = (node: HTMLElement): Rect => ({\n left: node.offsetLeft,\n top: node.offsetTop,\n width: node.offsetWidth,\n height: node.offsetHeight,\n});\n\ninterface Word {\n key: string;\n word: string;\n /** 0 for the words the text mounted with, which do not animate in. */\n seq: number;\n}\n\ninterface LeavingWord {\n id: number;\n word: string;\n rect: Rect;\n}\n\nfunction LeavingWordNode({\n item,\n onDone,\n exit,\n}: {\n item: LeavingWord;\n onDone: (id: number) => void;\n exit: WordExit;\n}) {\n const { id } = item;\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(true, remove);\n return (\n <span\n aria-hidden=\"true\"\n onAnimationEnd={(event) => {\n if (event.target === event.currentTarget) remove();\n }}\n style={item.rect}\n className={WORD_OUT[exit]}\n >\n {item.word}\n </span>\n );\n}\n\ninterface Column {\n /** Distance from the end of the number, so a new leading digit adds a column on the left. */\n key: number;\n char: string;\n seq: number;\n}\n\nfunction LeavingDigit({\n id,\n rect,\n onDone,\n children,\n}: {\n id: number;\n rect: Rect;\n onDone: (id: number) => void;\n children: string;\n}) {\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(true, remove, SPRING_BACKSTOP);\n // Reduced motion swaps instantly, and an animation that is switched off never ends.\n useIsomorphicLayoutEffect(() => {\n if (prefersReducedMotion()) remove();\n }, [remove]);\n return (\n <span\n aria-hidden=\"true\"\n onAnimationEnd={(event) => {\n if (event.target === event.currentTarget && event.animationName === \"field-digit-move-out\")\n remove();\n }}\n style={rect}\n className=\"absolute motion-safe:animate-field-digit-out\"\n >\n {children}\n </span>\n );\n}\n\nfunction DigitColumn({\n column,\n direction,\n leaving,\n onDone,\n}: {\n column: Column;\n direction: number;\n leaving: boolean;\n onDone: (key: number) => void;\n}) {\n const digitRef = useRef<HTMLSpanElement>(null);\n const digits = useLeavingItems(column.char, column.char, () => digitRef.current);\n const done = useCallback(() => onDone(column.key), [onDone, column.key]);\n useExitBackstop(leaving, done, SPRING_BACKSTOP);\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) done();\n }, [leaving, done]);\n return (\n <span\n style={{ \"--roll\": direction } as CSSProperties}\n onAnimationEnd={(event) => {\n if (\n leaving &&\n event.target === event.currentTarget &&\n event.animationName === \"field-column-shrink\"\n )\n done();\n }}\n className={cn(\n \"field-number-column\",\n leaving\n ? \"motion-safe:animate-field-column-out\"\n : column.seq > 0 && \"motion-safe:animate-field-column-in\",\n )}\n >\n {digits.leaving.map((item) => (\n <LeavingDigit key={item.id} id={item.id} rect={item.rect} onDone={digits.remove}>\n {item.value}\n </LeavingDigit>\n ))}\n <span\n key={column.char}\n ref={digitRef}\n className={digits.changed ? \"motion-safe:animate-field-digit-in\" : undefined}\n >\n {column.char}\n </span>\n </span>\n );\n}\n\n/**\n * A count in the copy, such as \"12 characters\": only the digits that changed\n * roll, and a gained or lost digit opens or closes its width. Digits arrive\n * from below when the number grew and from above when it shrank.\n */\nfunction RollingNumber({ word }: { word: string }) {\n const [state, setState] = useState(() => ({\n word,\n direction: 1,\n seq: 0,\n columns: toColumns(word, 0, new Map<number, Column>()),\n gone: [] as Column[],\n }));\n let { columns, gone, direction } = state;\n if (state.word !== word) {\n const seq = state.seq + 1;\n direction =\n parseFloat(word.replace(/,/g, \"\")) < parseFloat(state.word.replace(/,/g, \"\")) ? -1 : 1;\n columns = toColumns(word, seq, new Map(state.columns.map((column) => [column.key, column])));\n const kept = new Set(columns.map((column) => column.key));\n // A column that comes back while it is closing is shown again, not kept twice.\n gone = [\n ...state.gone.filter((column) => !kept.has(column.key)),\n ...state.columns.filter((column) => !kept.has(column.key)),\n ];\n setState({ word, direction, seq, columns, gone });\n }\n const finish = useCallback(\n (key: number) =>\n setState((current) => ({\n ...current,\n gone: current.gone.filter((column) => column.key !== key),\n })),\n [],\n );\n // Left to right is key descending, so a closing column keeps its place while it shrinks.\n const all = [\n ...columns.map((column) => ({ column, leaving: false })),\n ...gone.map((column) => ({ column, leaving: true })),\n ].sort((a, b) => b.column.key - a.column.key);\n return (\n <>\n {all.map(({ column, leaving }) => (\n <DigitColumn\n key={column.key}\n column={column}\n direction={direction}\n leaving={leaving}\n onDone={finish}\n />\n ))}\n </>\n );\n}\n\nfunction toColumns(word: string, seq: number, previous: Map<number, Column>): Column[] {\n const chars = word.split(\"\");\n return chars.map((char, index) => {\n const key = chars.length - index;\n const old = previous.get(key);\n return { key, char, seq: old ? old.seq : seq };\n });\n}\n\n/**\n * Copy that changes word by word. Words are keyed by position (numbers keep\n * their identity, so only their digits move), so inserting a word near the\n * front replaces every word after it. A changed word rises in from a soft blur\n * and the old one lifts away from where it stood. Assistive technology reads\n * the plain copy; the animated copy is hidden from it.\n */\nfunction MotionText({ text, exit, numbers }: { text: string; exit: WordExit; numbers: boolean }) {\n const [state, setState] = useState(() => ({\n text,\n seq: 0,\n words: toWords(text, 0, new Map<string, Word>(), numbers),\n }));\n let { words } = state;\n if (state.text !== text) {\n const seq = state.seq + 1;\n words = toWords(text, seq, new Map(state.words.map((word) => [word.key, word])), numbers);\n setState({ text, seq, words });\n }\n\n const rowRef = useRef<HTMLSpanElement>(null);\n const before = useRef(new Map<string, { rect: Rect; word: string }>());\n const nextId = useRef(0);\n const [leaving, setLeaving] = useState<LeavingWord[]>([]);\n\n // After every commit: remember where each word stands, and keep the ones that are gone, where they stood.\n useIsomorphicLayoutEffect(() => {\n const row = rowRef.current;\n if (!row) return;\n const now = new Map<string, { rect: Rect; word: string }>();\n for (const node of row.querySelectorAll<HTMLElement>(\"[data-word]\")) {\n now.set(node.dataset.word!, { rect: rectOf(node), word: node.textContent ?? \"\" });\n }\n const gone: LeavingWord[] = [];\n // Reduced motion removes a replaced word at once, and a box with no size has nothing to lift away.\n if (!prefersReducedMotion()) {\n for (const [key, was] of before.current) {\n if (!now.has(key) && was.rect.width && was.rect.height)\n gone.push({ id: nextId.current++, word: was.word, rect: was.rect });\n }\n }\n before.current = now;\n if (gone.length) setLeaving((list) => [...list, ...gone]);\n });\n\n const finish = useCallback(\n (id: number) => setLeaving((list) => list.filter((item) => item.id !== id)),\n [],\n );\n\n return (\n <>\n <span className=\"sr-only\">{text}</span>\n <span ref={rowRef} aria-hidden=\"true\" className=\"relative block\">\n {leaving.map((item) => (\n <LeavingWordNode key={item.id} item={item} onDone={finish} exit={exit} />\n ))}\n {words.map((entry, index) => (\n <span\n key={entry.key}\n data-word={entry.key}\n className={cn(\n \"inline-block whitespace-pre\",\n entry.seq > 0 && \"motion-safe:animate-field-word-in motion-reduce:animate-fade-in\",\n )}\n >\n {numbers && isNumber(entry.word) ? <RollingNumber word={entry.word} /> : entry.word}\n {index < words.length - 1 ? \" \" : null}\n </span>\n ))}\n </span>\n </>\n );\n}\n\nfunction toWords(text: string, seq: number, previous: Map<string, Word>, numbers: boolean): Word[] {\n return text.split(\" \").map((word, index) => {\n const key = `${index}:${numbers && isNumber(word) ? \"#\" : word}`;\n const old = previous.get(key);\n return { key, word, seq: old ? old.seq : seq };\n });\n}\n\ninterface Row {\n text: string;\n /** entering: mounted closed, about to open. closing: kept on screen while it shuts. */\n phase: \"entering\" | \"open\" | \"closing\";\n /** False for a row the field mounted with, whose copy does not settle in. */\n animate: boolean;\n}\n\n/** Writes the copy's height where the slot's height transition reads it. */\nfunction syncHeight(slot: HTMLElement, copy: HTMLElement) {\n const height = copy.getBoundingClientRect().height;\n if (height) slot.style.setProperty(\"--field-message-h\", height + \"px\");\n}\n\n/**\n * A field's helper or error row. `text` empty means no row. A row that appears\n * after mount opens its height on a spring and its copy settles in; one that\n * goes away closes the same way, still showing its last text while it does, and\n * text that comes back mid-close reopens the same row. A row present at first\n * render never animates. Error copy is an alert.\n *\n * The slot's height is written from script (a transition cannot start from or\n * end at auto): measured in a layout effect before the row opens, and again\n * whenever the copy resizes, so a message that wraps opens its next line.\n */\nexport function FieldMessage({\n id,\n text,\n tone,\n wordExit = \"settle\",\n numbers = true,\n copy = \"words\",\n}: {\n id?: string | undefined;\n text?: string | undefined;\n tone: \"description\" | \"error\";\n /** How a replaced word leaves (Input settles in 180ms, Textarea in 160ms, Password field uses the sharper exit curve). */\n wordExit?: WordExit;\n /** Roll digits in counts such as \"12 characters\". Off for copy that is words only. */\n numbers?: boolean;\n /** `\"words\"` animates a changed message word by word; `\"swap\"` replaces it as a whole, rising in as the old one lifts away. */\n copy?: \"words\" | \"swap\";\n}) {\n // Decided during render, so the id exists in the same commit as the aria-describedby that names it.\n const [row, setRow] = useState<Row | null>(() =>\n text ? { text, phase: \"open\", animate: false } : null,\n );\n if (text && (!row || row.text !== text || row.phase === \"closing\")) {\n setRow(\n row\n ? { text, phase: row.phase === \"closing\" ? \"open\" : row.phase, animate: row.animate }\n : { text, phase: \"entering\", animate: true },\n );\n } else if (!text && row && row.phase !== \"closing\") {\n setRow({ ...row, phase: \"closing\" });\n }\n\n const slotRef = useRef<HTMLSpanElement>(null);\n const copyRef = useRef<HTMLSpanElement>(null);\n const phase = row?.phase;\n\n // Measure before the row opens, and force the closed style to be computed first so there is something to transition from.\n useIsomorphicLayoutEffect(() => {\n const slot = slotRef.current;\n const copy = copyRef.current;\n if (!slot || !copy || phase === \"closing\") return;\n syncHeight(slot, copy);\n if (phase === \"entering\") {\n void slot.offsetHeight;\n setRow((current) =>\n current?.phase === \"entering\" ? { ...current, phase: \"open\" } : current,\n );\n }\n });\n\n // Remove the row once its height has closed. A row that never opened, or reduced motion, has nothing to wait for.\n useIsomorphicLayoutEffect(() => {\n if (phase !== \"closing\") return;\n const slot = slotRef.current;\n const gone = () => setRow((current) => (current?.phase === \"closing\" ? null : current));\n if (!slot || prefersReducedMotion()) {\n gone();\n return;\n }\n const style = getComputedStyle(slot);\n if (parseFloat(style.height) === 0 || style.opacity === \"0\") {\n gone();\n return;\n }\n const onEnd = (event: TransitionEvent) => {\n if (event.target === slot && event.propertyName === \"height\") gone();\n };\n slot.addEventListener(\"transitionend\", onEnd);\n const backstop = setTimeout(gone, SPRING_BACKSTOP);\n return () => {\n slot.removeEventListener(\"transitionend\", onEnd);\n clearTimeout(backstop);\n };\n }, [phase]);\n\n // Later size changes only (the first measurement is the layout effect above). A frame later, so the write cannot report a ResizeObserver loop.\n const mounted = row !== null;\n useEffect(() => {\n const slot = slotRef.current;\n const copy = copyRef.current;\n if (!slot || !copy || typeof ResizeObserver === \"undefined\") return;\n let frame = 0;\n const observer = new ResizeObserver(([entry]) => {\n const height = entry?.borderBoxSize[0]?.blockSize ?? copy.offsetHeight;\n if (!height) return;\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() =>\n slot.style.setProperty(\"--field-message-h\", height + \"px\"),\n );\n });\n observer.observe(copy);\n return () => {\n cancelAnimationFrame(frame);\n observer.disconnect();\n };\n }, [mounted]);\n\n if (!row) return null;\n const closing = row.phase === \"closing\";\n return (\n <span\n ref={slotRef}\n data-slot=\"field-message-slot\"\n data-state={closing || row.phase === \"entering\" ? \"closed\" : \"open\"}\n className=\"field-message-slot\"\n >\n <span\n ref={copyRef}\n // A closing row no longer describes the field: it gives up its id and its alert role.\n id={closing ? undefined : id}\n role={tone === \"error\" && !closing ? \"alert\" : undefined}\n className={cn(\n \"block pt-2 text-sm leading-(--leading-normal) \" +\n (tone === \"error\" ? \"text-destructive-strong\" : \"text-muted-foreground\") +\n (row.animate ? \" motion-safe:animate-field-copy-in\" : \"\"),\n )}\n >\n {copy === \"swap\" ? (\n <>\n <span className=\"sr-only\">{row.text}</span>\n <span aria-hidden=\"true\" className=\"block\">\n <SwapText swapKey={row.text} className=\"block\">\n {row.text}\n </SwapText>\n </span>\n </>\n ) : (\n <MotionText text={row.text} exit={wordExit} numbers={numbers} />\n )}\n </span>\n </span>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"field-message.js","names":[],"sources":["../../src/lib/field-message.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState, type CSSProperties, type RefObject } from \"react\";\n\nimport { cn } from \"./cn.js\";\nimport {\n prefersReducedMotion,\n useExitBackstop,\n useIsomorphicLayoutEffect,\n useLeavingItems,\n type Rect,\n} from \"./motion.js\";\nimport { SwapText } from \"./swap-text.js\";\n\n/*\n * The helper and error copy under a text field. Three layers of presence, each\n * a small piece of React because CSS cannot keep a node that React has already\n * unmounted: the row (opens and closes its height), the words (a changed word\n * rises in while the old one lifts away) and the numbers (one column per digit,\n * a gained or lost digit opens or closes its width, a changed digit rolls).\n *\n * Nothing animates on first mount at any layer: a row present at first render,\n * words in a row that has just opened, and digits in a column that has just\n * opened all appear as they are.\n */\n\nconst isNumber = (word: string) => /^\\d[\\d.,/:]*%?$/.test(word);\n\n/** How a replaced word leaves: Input's 180ms settle, Textarea's 160ms, or the sharper ease-exit curve Password field uses. */\nexport type WordExit = \"settle\" | \"quick\" | \"sharp\";\n\nconst WORD_OUT: Record<WordExit, string> = {\n settle: \"absolute inline-block whitespace-pre motion-safe:animate-field-word-out\",\n quick: \"absolute inline-block whitespace-pre motion-safe:animate-field-word-out-quick\",\n sharp: \"absolute inline-block whitespace-pre motion-safe:animate-field-word-out-sharp\",\n};\n\n/** The longest a spring exit takes (736ms) plus a margin, for the backstop that removes a node whose animation never ends. */\nconst SPRING_BACKSTOP = 900;\n\nconst rectOf = (node: HTMLElement): Rect => ({\n left: node.offsetLeft,\n top: node.offsetTop,\n width: node.offsetWidth,\n height: node.offsetHeight,\n});\n\ninterface Word {\n key: string;\n word: string;\n /** 0 for the words the text mounted with, which do not animate in. */\n seq: number;\n}\n\ninterface LeavingWord {\n id: number;\n word: string;\n rect: Rect;\n}\n\nfunction LeavingWordNode({\n item,\n onDone,\n exit,\n}: {\n item: LeavingWord;\n onDone: (id: number) => void;\n exit: WordExit;\n}) {\n const { id } = item;\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(true, remove);\n return (\n <span\n aria-hidden=\"true\"\n onAnimationEnd={(event) => {\n if (event.target === event.currentTarget) remove();\n }}\n style={item.rect}\n className={WORD_OUT[exit]}\n >\n {item.word}\n </span>\n );\n}\n\ninterface Column {\n /** Distance from the end of the number, so a new leading digit adds a column on the left. */\n key: number;\n char: string;\n seq: number;\n}\n\nfunction LeavingDigit({\n id,\n rect,\n onDone,\n children,\n}: {\n id: number;\n rect: Rect;\n onDone: (id: number) => void;\n children: string;\n}) {\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(true, remove, SPRING_BACKSTOP);\n // Reduced motion swaps instantly, and an animation that is switched off never ends.\n useIsomorphicLayoutEffect(() => {\n if (prefersReducedMotion()) remove();\n }, [remove]);\n return (\n <span\n aria-hidden=\"true\"\n onAnimationEnd={(event) => {\n if (event.target === event.currentTarget && event.animationName === \"field-digit-move-out\")\n remove();\n }}\n style={rect}\n className=\"absolute motion-safe:animate-field-digit-out\"\n >\n {children}\n </span>\n );\n}\n\n/** One digit, rolling in or not as it was when it mounted: a later change of `animate` must not replay it. */\nfunction Digit({\n nodeRef,\n animate,\n children,\n}: {\n nodeRef: RefObject<HTMLSpanElement | null>;\n animate: boolean;\n children: string;\n}) {\n const [rolls] = useState(animate);\n return (\n <span ref={nodeRef} className={rolls ? \"motion-safe:animate-field-digit-in\" : undefined}>\n {children}\n </span>\n );\n}\n\nfunction DigitColumn({\n column,\n direction,\n leaving,\n still,\n onDone,\n}: {\n column: Column;\n direction: number;\n leaving: boolean;\n /** Show the digits as they are, without rolling or opening columns. */\n still: boolean;\n onDone: (key: number) => void;\n}) {\n const digitRef = useRef<HTMLSpanElement>(null);\n const digits = useLeavingItems(column.char, column.char, () => digitRef.current);\n // A digit that arrives after the column is already there rolls in; the one it mounted with does not.\n const [shownDigit, setShownDigit] = useState({ char: column.char, changes: 0 });\n if (shownDigit.char !== column.char)\n setShownDigit({ char: column.char, changes: shownDigit.changes + 1 });\n const done = useCallback(() => onDone(column.key), [onDone, column.key]);\n useExitBackstop(leaving, done, SPRING_BACKSTOP);\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) done();\n }, [leaving, done]);\n return (\n <span\n style={{ \"--roll\": direction } as CSSProperties}\n onAnimationEnd={(event) => {\n if (\n leaving &&\n event.target === event.currentTarget &&\n event.animationName === \"field-column-shrink\"\n )\n done();\n }}\n className={cn(\n \"field-number-column\",\n leaving\n ? \"motion-safe:animate-field-column-out\"\n : column.seq > 0 && \"motion-safe:animate-field-column-in\",\n )}\n >\n {(still ? [] : digits.leaving).map((item) => (\n <LeavingDigit key={item.id} id={item.id} rect={item.rect} onDone={digits.remove}>\n {item.value}\n </LeavingDigit>\n ))}\n <Digit key={column.char} nodeRef={digitRef} animate={shownDigit.changes > 0 && !still}>\n {column.char}\n </Digit>\n </span>\n );\n}\n\n/**\n * A count in the copy, such as \"12 characters\": only the digits that changed\n * roll, and a gained or lost digit opens or closes its width. Digits arrive\n * from below when the number grew and from above when it shrank.\n */\nexport function RollingNumber({\n word,\n direction: forced,\n still = false,\n}: {\n word: string;\n /** Which way the digits roll (1 up, -1 down). Read from the number's own change by default, which fails for text such as \"$12\". */\n direction?: 1 | -1;\n /** Follow the text at once, with no rolling: for a number that is being typed. */\n still?: boolean;\n}) {\n const [state, setState] = useState(() => ({\n word,\n direction: 1,\n seq: 0,\n columns: toColumns(word, 0, new Map<number, Column>()),\n gone: [] as Column[],\n }));\n let { columns, gone, direction } = state;\n if (state.word !== word) {\n const seq = state.seq + 1;\n direction =\n forced ??\n (parseFloat(word.replace(/,/g, \"\")) < parseFloat(state.word.replace(/,/g, \"\")) ? -1 : 1);\n // A still number opens its new columns at once: seq 0 is the seq of a column the text mounted with.\n columns = toColumns(\n word,\n still ? 0 : seq,\n new Map(state.columns.map((column) => [column.key, column])),\n );\n const kept = new Set(columns.map((column) => column.key));\n // A column that comes back while it is closing is shown again, not kept twice.\n gone = still\n ? []\n : [\n ...state.gone.filter((column) => !kept.has(column.key)),\n ...state.columns.filter((column) => !kept.has(column.key)),\n ];\n setState({ word, direction, seq, columns, gone });\n }\n const finish = useCallback(\n (key: number) =>\n setState((current) => ({\n ...current,\n gone: current.gone.filter((column) => column.key !== key),\n })),\n [],\n );\n // Left to right is key descending, so a closing column keeps its place while it shrinks.\n const all = [\n ...columns.map((column) => ({ column, leaving: false })),\n ...gone.map((column) => ({ column, leaving: true })),\n ].sort((a, b) => b.column.key - a.column.key);\n return (\n <>\n {all.map(({ column, leaving }) => (\n <DigitColumn\n key={column.key}\n column={column}\n direction={direction}\n leaving={leaving}\n still={still}\n onDone={finish}\n />\n ))}\n </>\n );\n}\n\nfunction toColumns(word: string, seq: number, previous: Map<number, Column>): Column[] {\n const chars = word.split(\"\");\n return chars.map((char, index) => {\n const key = chars.length - index;\n const old = previous.get(key);\n return { key, char, seq: old ? old.seq : seq };\n });\n}\n\n/**\n * Copy that changes word by word. Words are keyed by position (numbers keep\n * their identity, so only their digits move), so inserting a word near the\n * front replaces every word after it. A changed word rises in from a soft blur\n * and the old one lifts away from where it stood. Assistive technology reads\n * the plain copy; the animated copy is hidden from it.\n */\nfunction MotionText({ text, exit, numbers }: { text: string; exit: WordExit; numbers: boolean }) {\n const [state, setState] = useState(() => ({\n text,\n seq: 0,\n words: toWords(text, 0, new Map<string, Word>(), numbers),\n }));\n let { words } = state;\n if (state.text !== text) {\n const seq = state.seq + 1;\n words = toWords(text, seq, new Map(state.words.map((word) => [word.key, word])), numbers);\n setState({ text, seq, words });\n }\n\n const rowRef = useRef<HTMLSpanElement>(null);\n const before = useRef(new Map<string, { rect: Rect; word: string }>());\n const nextId = useRef(0);\n const [leaving, setLeaving] = useState<LeavingWord[]>([]);\n\n // After every commit: remember where each word stands, and keep the ones that are gone, where they stood.\n useIsomorphicLayoutEffect(() => {\n const row = rowRef.current;\n if (!row) return;\n const now = new Map<string, { rect: Rect; word: string }>();\n for (const node of row.querySelectorAll<HTMLElement>(\"[data-word]\")) {\n now.set(node.dataset.word!, { rect: rectOf(node), word: node.textContent ?? \"\" });\n }\n const gone: LeavingWord[] = [];\n // Reduced motion removes a replaced word at once, and a box with no size has nothing to lift away.\n if (!prefersReducedMotion()) {\n for (const [key, was] of before.current) {\n if (!now.has(key) && was.rect.width && was.rect.height)\n gone.push({ id: nextId.current++, word: was.word, rect: was.rect });\n }\n }\n before.current = now;\n if (gone.length) setLeaving((list) => [...list, ...gone]);\n });\n\n const finish = useCallback(\n (id: number) => setLeaving((list) => list.filter((item) => item.id !== id)),\n [],\n );\n\n return (\n <>\n <span className=\"sr-only\">{text}</span>\n <span ref={rowRef} aria-hidden=\"true\" className=\"relative block\">\n {leaving.map((item) => (\n <LeavingWordNode key={item.id} item={item} onDone={finish} exit={exit} />\n ))}\n {words.map((entry, index) => (\n <span\n key={entry.key}\n data-word={entry.key}\n className={cn(\n \"inline-block whitespace-pre\",\n entry.seq > 0 && \"motion-safe:animate-field-word-in motion-reduce:animate-fade-in\",\n )}\n >\n {numbers && isNumber(entry.word) ? <RollingNumber word={entry.word} /> : entry.word}\n {index < words.length - 1 ? \" \" : null}\n </span>\n ))}\n </span>\n </>\n );\n}\n\nfunction toWords(text: string, seq: number, previous: Map<string, Word>, numbers: boolean): Word[] {\n return text.split(\" \").map((word, index) => {\n const key = `${index}:${numbers && isNumber(word) ? \"#\" : word}`;\n const old = previous.get(key);\n return { key, word, seq: old ? old.seq : seq };\n });\n}\n\ninterface Row {\n text: string;\n /** entering: mounted closed, about to open. closing: kept on screen while it shuts. */\n phase: \"entering\" | \"open\" | \"closing\";\n /** False for a row the field mounted with, whose copy does not settle in. */\n animate: boolean;\n}\n\n/** Writes the copy's height where the slot's height transition reads it. */\nfunction syncHeight(slot: HTMLElement, copy: HTMLElement) {\n const height = copy.getBoundingClientRect().height;\n if (height) slot.style.setProperty(\"--field-message-h\", height + \"px\");\n}\n\n/**\n * A field's helper or error row. `text` empty means no row. A row that appears\n * after mount opens its height on a spring and its copy settles in; one that\n * goes away closes the same way, still showing its last text while it does, and\n * text that comes back mid-close reopens the same row. A row present at first\n * render never animates. Error copy is an alert.\n *\n * The slot's height is written from script (a transition cannot start from or\n * end at auto): measured in a layout effect before the row opens, and again\n * whenever the copy resizes, so a message that wraps opens its next line.\n */\nexport function FieldMessage({\n id,\n text,\n tone,\n wordExit = \"settle\",\n numbers = true,\n copy = \"words\",\n}: {\n id?: string | undefined;\n text?: string | undefined;\n tone: \"description\" | \"error\";\n /** How a replaced word leaves (Input settles in 180ms, Textarea in 160ms, Password field uses the sharper exit curve). */\n wordExit?: WordExit;\n /** Roll digits in counts such as \"12 characters\". Off for copy that is words only. */\n numbers?: boolean;\n /** `\"words\"` animates a changed message word by word; `\"swap\"` replaces it as a whole, rising in as the old one lifts away. */\n copy?: \"words\" | \"swap\";\n}) {\n // Decided during render, so the id exists in the same commit as the aria-describedby that names it.\n const [row, setRow] = useState<Row | null>(() =>\n text ? { text, phase: \"open\", animate: false } : null,\n );\n if (text && (!row || row.text !== text || row.phase === \"closing\")) {\n setRow(\n row\n ? { text, phase: row.phase === \"closing\" ? \"open\" : row.phase, animate: row.animate }\n : { text, phase: \"entering\", animate: true },\n );\n } else if (!text && row && row.phase !== \"closing\") {\n setRow({ ...row, phase: \"closing\" });\n }\n\n const slotRef = useRef<HTMLSpanElement>(null);\n const copyRef = useRef<HTMLSpanElement>(null);\n const phase = row?.phase;\n\n // Measure before the row opens, and force the closed style to be computed first so there is something to transition from.\n useIsomorphicLayoutEffect(() => {\n const slot = slotRef.current;\n const copy = copyRef.current;\n if (!slot || !copy || phase === \"closing\") return;\n syncHeight(slot, copy);\n if (phase === \"entering\") {\n void slot.offsetHeight;\n setRow((current) =>\n current?.phase === \"entering\" ? { ...current, phase: \"open\" } : current,\n );\n }\n });\n\n // Remove the row once its height has closed. A row that never opened, or reduced motion, has nothing to wait for.\n useIsomorphicLayoutEffect(() => {\n if (phase !== \"closing\") return;\n const slot = slotRef.current;\n const gone = () => setRow((current) => (current?.phase === \"closing\" ? null : current));\n if (!slot || prefersReducedMotion()) {\n gone();\n return;\n }\n const style = getComputedStyle(slot);\n if (parseFloat(style.height) === 0 || style.opacity === \"0\") {\n gone();\n return;\n }\n const onEnd = (event: TransitionEvent) => {\n if (event.target === slot && event.propertyName === \"height\") gone();\n };\n slot.addEventListener(\"transitionend\", onEnd);\n const backstop = setTimeout(gone, SPRING_BACKSTOP);\n return () => {\n slot.removeEventListener(\"transitionend\", onEnd);\n clearTimeout(backstop);\n };\n }, [phase]);\n\n // Later size changes only (the first measurement is the layout effect above). A frame later, so the write cannot report a ResizeObserver loop.\n const mounted = row !== null;\n useEffect(() => {\n const slot = slotRef.current;\n const copy = copyRef.current;\n if (!slot || !copy || typeof ResizeObserver === \"undefined\") return;\n let frame = 0;\n const observer = new ResizeObserver(([entry]) => {\n const height = entry?.borderBoxSize[0]?.blockSize ?? copy.offsetHeight;\n if (!height) return;\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() =>\n slot.style.setProperty(\"--field-message-h\", height + \"px\"),\n );\n });\n observer.observe(copy);\n return () => {\n cancelAnimationFrame(frame);\n observer.disconnect();\n };\n }, [mounted]);\n\n if (!row) return null;\n const closing = row.phase === \"closing\";\n return (\n <span\n ref={slotRef}\n data-slot=\"field-message-slot\"\n data-state={closing || row.phase === \"entering\" ? \"closed\" : \"open\"}\n className=\"field-message-slot\"\n >\n <span\n ref={copyRef}\n // A closing row no longer describes the field: it gives up its id and its alert role.\n id={closing ? undefined : id}\n role={tone === \"error\" && !closing ? \"alert\" : undefined}\n className={cn(\n \"block pt-2 text-sm leading-(--leading-normal) \" +\n (tone === \"error\" ? \"text-destructive-strong\" : \"text-muted-foreground\") +\n (row.animate ? \" motion-safe:animate-field-copy-in\" : \"\"),\n )}\n >\n {copy === \"swap\" ? (\n <>\n <span className=\"sr-only\">{row.text}</span>\n <span aria-hidden=\"true\" className=\"block\">\n <SwapText swapKey={row.text} className=\"block\">\n {row.text}\n </SwapText>\n </span>\n </>\n ) : (\n <MotionText text={row.text} exit={wordExit} numbers={numbers} />\n )}\n </span>\n </span>\n );\n}\n"],"mappings":"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"}
|
|
@@ -20,20 +20,21 @@ import { cva } from "class-variance-authority";
|
|
|
20
20
|
const fieldSurfaceVariants = cva([
|
|
21
21
|
"w-full min-w-0",
|
|
22
22
|
"box-border font-sans",
|
|
23
|
-
"rounded-
|
|
24
|
-
"bg-
|
|
23
|
+
"rounded-control border-(length:--border-width) border-border",
|
|
24
|
+
"bg-surface text-foreground",
|
|
25
25
|
"shadow-surface",
|
|
26
26
|
"placeholder:text-muted-foreground",
|
|
27
27
|
"selection:bg-primary selection:text-primary-foreground",
|
|
28
28
|
"motion-safe:transition-[border-color,background-color] motion-safe:duration-(--duration-quick) motion-safe:ease-settle",
|
|
29
29
|
"outline-none",
|
|
30
|
-
"data-[focus-visible]:outline-solid",
|
|
31
|
-
"data-[focus-visible]:outline-(length:--focus-ring-width)",
|
|
32
|
-
"data-[focus-visible]:outline-offset-(--focus-ring-offset)",
|
|
33
|
-
"data-[focus-visible]:outline-(--color-ring)",
|
|
30
|
+
"forced-colors:data-[focus-visible]:outline-solid",
|
|
31
|
+
"forced-colors:data-[focus-visible]:outline-(length:--focus-ring-width)",
|
|
32
|
+
"forced-colors:data-[focus-visible]:outline-offset-(--focus-ring-offset)",
|
|
34
33
|
"data-[invalid]:border-destructive-strong",
|
|
35
|
-
"[&:where([data-hovered]:not([data-disabled]))]:border-
|
|
36
|
-
"
|
|
34
|
+
"[&:where([data-hovered]:not([data-disabled]))]:border-border-strong",
|
|
35
|
+
"data-[focus-visible]:not-data-[invalid]:not-group-data-[invalid]:border-foreground",
|
|
36
|
+
"[&:is(input,textarea):focus:not([data-invalid])]:border-foreground",
|
|
37
|
+
"[&:has(:is(input,textarea,[role=spinbutton]):focus):not([data-invalid]):not(:where(.group[data-invalid]_*))]:border-foreground",
|
|
37
38
|
"data-[disabled]:cursor-not-allowed data-[disabled]:bg-muted data-[disabled]:opacity-(--opacity-disabled)"
|
|
38
39
|
]);
|
|
39
40
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-surface.js","names":[],"sources":["../../src/lib/field-surface.ts"],"sourcesContent":["import { cva } from \"class-variance-authority\";\n\n/**\n * The shared surface of a text entry control.\n *\n * Extracted at the Rule of Two, and for a reason the legacy demonstrates:\n * its `input.tsx` and `textarea.tsx` styled the same surface twice and had\n * already drifted. The input is `bg-transparent`, the textarea is\n * `bg-background`. The input focuses with `focus-visible:border-ring` plus a\n * 3px ring; the textarea uses a 2px ring with an offset. The input has\n * `aria-invalid` treatment, the textarea has none. None of those differences\n * is a decision anyone made -- they are two files edited at different times.\n *\n * Everything that must look identical between an Input and a Textarea lives\n * here. Only the box geometry differs, and that is the caller's variant.\n *\n * Every string is a STATIC literal. Tailwind scans source text, so a class\n * assembled at runtime is invisible to it and would never be emitted.\n */\nexport const fieldSurfaceVariants = cva([\n \"w-full min-w-0\",\n\n // The reset in @seekalfred/styles is OPT-IN, so a consumer may load none.\n // A bare <input> keeps the user-agent border, background and Arial face,\n // and content-box would add the padding on top of the width.\n \"box-border font-sans\",\n\n \"rounded-
|
|
1
|
+
{"version":3,"file":"field-surface.js","names":[],"sources":["../../src/lib/field-surface.ts"],"sourcesContent":["import { cva } from \"class-variance-authority\";\n\n/**\n * The shared surface of a text entry control.\n *\n * Extracted at the Rule of Two, and for a reason the legacy demonstrates:\n * its `input.tsx` and `textarea.tsx` styled the same surface twice and had\n * already drifted. The input is `bg-transparent`, the textarea is\n * `bg-background`. The input focuses with `focus-visible:border-ring` plus a\n * 3px ring; the textarea uses a 2px ring with an offset. The input has\n * `aria-invalid` treatment, the textarea has none. None of those differences\n * is a decision anyone made -- they are two files edited at different times.\n *\n * Everything that must look identical between an Input and a Textarea lives\n * here. Only the box geometry differs, and that is the caller's variant.\n *\n * Every string is a STATIC literal. Tailwind scans source text, so a class\n * assembled at runtime is invisible to it and would never be emitted.\n */\nexport const fieldSurfaceVariants = cva([\n \"w-full min-w-0\",\n\n // The reset in @seekalfred/styles is OPT-IN, so a consumer may load none.\n // A bare <input> keeps the user-agent border, background and Arial face,\n // and content-box would add the padding on top of the width.\n \"box-border font-sans\",\n\n \"rounded-control border-(length:--border-width) border-border\",\n \"bg-surface text-foreground\",\n \"shadow-surface\",\n\n // --color-muted-foreground is 5.25:1 on the page, so the placeholder is\n // legible rather than the usual near-invisible grey. It is still text and\n // the contrast contract holds it to the 4.5 floor.\n \"placeholder:text-muted-foreground\",\n \"selection:bg-primary selection:text-primary-foreground\",\n\n // Only the two properties that change, on the settle curve: the border darkens\n // on hover and focus, and the background turns muted when disabled. The focus\n // outline is not animated (it switches between none and solid).\n \"motion-safe:transition-[border-color,background-color] motion-safe:duration-(--duration-quick) motion-safe:ease-settle\",\n\n // outline-solid is load-bearing. `outline-none` sets BOTH\n // outline-style:none and --tw-outline-style:none, and the width utility\n // resolves its style from that same variable -- so a focus rule can apply\n // a width and a colour while faithfully re-applying none, and paint\n // nothing. Button shipped exactly that.\n //\n // A text field shows focus on its own border (below), not a separate ring:\n // UX decision (Oct 2026). The outline comes back only under forced colours,\n // where a border colour is overridden and would leave no focus indicator.\n \"outline-none\",\n \"forced-colors:data-[focus-visible]:outline-solid\",\n \"forced-colors:data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"forced-colors:data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n\n // React Aria sets data-invalid from the native `aria-invalid` attribute,\n // so this costs no API surface and works both bare and inside a\n // TextField. destructive-strong rather than destructive: the fill token\n // is 2.44:1-class and cannot describe a boundary (WCAG 1.4.11).\n //\n // The border CHANGES COLOUR only. Colour alone is not a state cue\n // (WCAG 1.4.1), which is why an invalid field must also carry a message --\n // that is the consumer's job, and the migration table says so.\n \"data-[invalid]:border-destructive-strong\",\n\n // Hover and focus darken the border to border-strong, in addition to the\n // outline ring above (which stays: forced-colours drops a border colour, and\n // WCAG 2.4.7 wants a visible indicator). The conditions sit inside :where(),\n // which adds no specificity, so the invalid border always wins by specificity\n // rather than by the order Tailwind happens to emit utilities in -- on an\n // Input through data-[invalid], on a Select or DatePicker trigger through\n // group-data-[invalid], because their ROOT carries the state. A disabled field\n // does not darken: React Aria's TextArea still reports hover while disabled.\n \"[&:where([data-hovered]:not([data-disabled]))]:border-border-strong\",\n // Focus turns the border itself dark, same width, no gap. An invalid field\n // keeps its red border while focused.\n \"data-[focus-visible]:not-data-[invalid]:not-group-data-[invalid]:border-foreground\",\n // A mouse click into a text box is not \"focus-visible\" to React Aria, but the\n // border must still show focus: key off real focus on the input itself, or on\n // the input, textarea or date segment inside a wrapping box.\n \"[&:is(input,textarea):focus:not([data-invalid])]:border-foreground\",\n \"[&:has(:is(input,textarea,[role=spinbutton]):focus):not([data-invalid]):not(:where(.group[data-invalid]_*))]:border-foreground\",\n\n \"data-[disabled]:cursor-not-allowed data-[disabled]:bg-muted data-[disabled]:opacity-(--opacity-disabled)\",\n]);\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,MAAa,uBAAuB,IAAI;CACtC;CAKA;CAEA;CACA;CACA;CAKA;CACA;CAKA;CAWA;CACA;CACA;CACA;CAUA;CAUA;CAGA;CAIA;CACA;CAEA;AACF,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
//#region src/lib/flag-emoji.ts
|
|
3
|
+
/**
|
|
4
|
+
* Whether this platform draws flag emoji. Windows ships none: a flag there comes
|
|
5
|
+
* out as two boxed letters, so the picker shows the plain ISO code instead.
|
|
6
|
+
*
|
|
7
|
+
* The test draws a flag in black on an offscreen canvas. A real flag glyph is
|
|
8
|
+
* coloured whatever the fill colour is; the fallback letters take the fill and
|
|
9
|
+
* stay black. Any saturated pixel means the platform can draw it.
|
|
10
|
+
*/
|
|
11
|
+
function detect() {
|
|
12
|
+
try {
|
|
13
|
+
const canvas = document.createElement("canvas");
|
|
14
|
+
canvas.width = 48;
|
|
15
|
+
canvas.height = 32;
|
|
16
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
17
|
+
if (!context) return false;
|
|
18
|
+
context.textBaseline = "top";
|
|
19
|
+
context.font = "24px sans-serif";
|
|
20
|
+
context.fillStyle = "black";
|
|
21
|
+
context.fillText("πΊπΈ", 0, 2);
|
|
22
|
+
const { data } = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
23
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
24
|
+
const spread = Math.max(data[i], data[i + 1], data[i + 2]) - Math.min(data[i], data[i + 1], data[i + 2]);
|
|
25
|
+
if (data[i + 3] > 0 && spread > 40) return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let cached;
|
|
33
|
+
/** The check runs once per page and its answer is kept. Browser only: it needs a canvas. */
|
|
34
|
+
function supportsFlagEmoji() {
|
|
35
|
+
if (typeof document === "undefined") return false;
|
|
36
|
+
return cached ??= detect();
|
|
37
|
+
}
|
|
38
|
+
const subscribe = () => () => {};
|
|
39
|
+
const serverSnapshot = () => false;
|
|
40
|
+
/**
|
|
41
|
+
* `false` on the server and for the hydrating render (so the markup matches),
|
|
42
|
+
* then the real answer. Never reads `window` at import time.
|
|
43
|
+
*/
|
|
44
|
+
function useFlagEmoji() {
|
|
45
|
+
return useSyncExternalStore(subscribe, supportsFlagEmoji, serverSnapshot);
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { supportsFlagEmoji, useFlagEmoji };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=flag-emoji.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flag-emoji.js","names":[],"sources":["../../src/lib/flag-emoji.ts"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\n\n/**\n * Whether this platform draws flag emoji. Windows ships none: a flag there comes\n * out as two boxed letters, so the picker shows the plain ISO code instead.\n *\n * The test draws a flag in black on an offscreen canvas. A real flag glyph is\n * coloured whatever the fill colour is; the fallback letters take the fill and\n * stay black. Any saturated pixel means the platform can draw it.\n */\nfunction detect(): boolean {\n try {\n const canvas = document.createElement(\"canvas\");\n canvas.width = 48;\n canvas.height = 32;\n const context = canvas.getContext(\"2d\", { willReadFrequently: true });\n if (!context) return false;\n context.textBaseline = \"top\";\n context.font = \"24px sans-serif\";\n context.fillStyle = \"black\";\n context.fillText(\"\\u{1F1FA}\\u{1F1F8}\", 0, 2);\n const { data } = context.getImageData(0, 0, canvas.width, canvas.height);\n for (let i = 0; i < data.length; i += 4) {\n const spread = Math.max(data[i]!, data[i + 1]!, data[i + 2]!) - Math.min(data[i]!, data[i + 1]!, data[i + 2]!);\n if (data[i + 3]! > 0 && spread > 40) return true;\n }\n return false;\n } catch {\n return false;\n }\n}\n\nlet cached: boolean | undefined;\n\n/** The check runs once per page and its answer is kept. Browser only: it needs a canvas. */\nexport function supportsFlagEmoji(): boolean {\n if (typeof document === \"undefined\") return false;\n return (cached ??= detect());\n}\n\nconst subscribe = () => () => {};\nconst serverSnapshot = () => false;\n\n/**\n * `false` on the server and for the hydrating render (so the markup matches),\n * then the real answer. Never reads `window` at import time.\n */\nexport function useFlagEmoji(): boolean {\n return useSyncExternalStore(subscribe, supportsFlagEmoji, serverSnapshot);\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAS,SAAkB;CACzB,IAAI;EACF,MAAM,SAAS,SAAS,cAAc,QAAQ;EAC9C,OAAO,QAAQ;EACf,OAAO,SAAS;EAChB,MAAM,UAAU,OAAO,WAAW,MAAM,EAAE,oBAAoB,KAAK,CAAC;EACpE,IAAI,CAAC,SAAS,OAAO;EACrB,QAAQ,eAAe;EACvB,QAAQ,OAAO;EACf,QAAQ,YAAY;EACpB,QAAQ,SAAS,QAAsB,GAAG,CAAC;EAC3C,MAAM,EAAE,SAAS,QAAQ,aAAa,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;EACvE,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;GACvC,MAAM,SAAS,KAAK,IAAI,KAAK,IAAK,KAAK,IAAI,IAAK,KAAK,IAAI,EAAG,IAAI,KAAK,IAAI,KAAK,IAAK,KAAK,IAAI,IAAK,KAAK,IAAI,EAAG;GAC7G,IAAI,KAAK,IAAI,KAAM,KAAK,SAAS,IAAI,OAAO;EAC9C;EACA,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AAEA,IAAI;;AAGJ,SAAgB,oBAA6B;CAC3C,IAAI,OAAO,aAAa,aAAa,OAAO;CAC5C,OAAQ,WAAW,OAAO;AAC5B;AAEA,MAAM,wBAAwB,CAAC;AAC/B,MAAM,uBAAuB;;;;;AAM7B,SAAgB,eAAwB;CACtC,OAAO,qBAAqB,WAAW,mBAAmB,cAAc;AAC1E"}
|
package/dist/lib/flip.js
CHANGED
|
@@ -16,19 +16,27 @@ import { useRef } from "react";
|
|
|
16
16
|
*
|
|
17
17
|
* @param duration CSS time of the slide, e.g. `var(--duration-standard)`.
|
|
18
18
|
* @param easing CSS easing of the slide, e.g. `var(--ease-enter)`.
|
|
19
|
+
* @param relative Measure positions from the container's own corner instead of the
|
|
20
|
+
* viewport's, so scrolling the page between two looks is not mistaken for the elements
|
|
21
|
+
* moving. For lists whose container stays put (rows of a table); content that also moves
|
|
22
|
+
* because its container does (centred text) wants the default.
|
|
19
23
|
*/
|
|
20
|
-
function useFlip(container, key, duration = "var(--duration-standard)", easing = "var(--ease-enter)") {
|
|
24
|
+
function useFlip(container, key, duration = "var(--duration-standard)", easing = "var(--ease-enter)", relative = false) {
|
|
21
25
|
const remembered = useRef(/* @__PURE__ */ new Map());
|
|
22
26
|
const measure = () => {
|
|
23
27
|
const root = container.current;
|
|
24
28
|
const next = /* @__PURE__ */ new Map();
|
|
25
29
|
if (!root) return next;
|
|
30
|
+
const origin = relative ? root.getBoundingClientRect() : {
|
|
31
|
+
left: 0,
|
|
32
|
+
top: 0
|
|
33
|
+
};
|
|
26
34
|
for (const el of root.querySelectorAll("[data-flip]")) {
|
|
27
35
|
if (el.closest("[data-leaving]")) continue;
|
|
28
36
|
const box = el.getBoundingClientRect();
|
|
29
37
|
next.set(el, {
|
|
30
|
-
left: box.left,
|
|
31
|
-
top: box.top
|
|
38
|
+
left: box.left - origin.left,
|
|
39
|
+
top: box.top - origin.top
|
|
32
40
|
});
|
|
33
41
|
}
|
|
34
42
|
return next;
|
package/dist/lib/flip.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flip.js","names":[],"sources":["../../src/lib/flip.ts"],"sourcesContent":["import { useRef, type RefObject } from \"react\";\n\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"./motion.js\";\n\n/**\n * Siblings that shift because something next to them changed size (a wider month\n * name pushes the day and year along) slide to their new place instead of\n * jumping: measure where each `[data-flip]` element was, let the new layout\n * commit, put each one back where it was with a translate, then release it on a\n * transition. CSS cannot do this alone, because the layout move is not a style\n * change.\n *\n * `key` says when to look. Positions are remembered after every look and\n * refreshed when the container resizes, so a move that is not a change of `key`\n * (a window resize) does not become the next flip. Reduced motion does not flip.\n * Elements inside a leaving copy (`[data-leaving]`) are ignored.\n *\n * @param duration CSS time of the slide, e.g. `var(--duration-standard)`.\n * @param easing CSS easing of the slide, e.g. `var(--ease-enter)`.\n */\nexport function useFlip(\n container: RefObject<HTMLElement | null>,\n key: unknown,\n duration = \"var(--duration-standard)\",\n easing = \"var(--ease-enter)\",\n) {\n const remembered = useRef(new Map<Element, { left: number; top: number }>());\n\n const measure = () => {\n const root = container.current;\n const next = new Map<Element, { left: number; top: number }>();\n if (!root) return next;\n for (const el of root.querySelectorAll<HTMLElement>(\"[data-flip]\")) {\n if (el.closest(\"[data-leaving]\")) continue;\n const box = el.getBoundingClientRect();\n next.set(el, { left: box.left, top: box.top });\n }\n return next;\n };\n\n useIsomorphicLayoutEffect(() => {\n const root = container.current;\n if (!root) return;\n const before = remembered.current;\n const after = measure();\n if (before.size > 0 && !prefersReducedMotion()) {\n const moved: HTMLElement[] = [];\n for (const [el, now] of after) {\n const was = before.get(el);\n if (!was) continue;\n const dx = was.left - now.left;\n const dy = was.top - now.top;\n if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) continue;\n const node = el as HTMLElement;\n node.style.transition = \"none\";\n node.style.translate = `${dx}px ${dy}px`;\n moved.push(node);\n }\n if (moved.length > 0) {\n void root.offsetWidth;\n for (const node of moved) {\n node.style.transition = `translate ${duration} ${easing}`;\n node.style.translate = \"\";\n node.addEventListener(\n \"transitionend\",\n () => {\n node.style.transition = \"\";\n },\n { once: true },\n );\n }\n }\n }\n remembered.current = after;\n // Positions are read once per change of `key`; a resize refreshes them below.\n }, [key]);\n\n useIsomorphicLayoutEffect(() => {\n const root = container.current;\n if (!root || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n remembered.current = measure();\n });\n observer.observe(root);\n return () => observer.disconnect();\n }, []);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"flip.js","names":[],"sources":["../../src/lib/flip.ts"],"sourcesContent":["import { useRef, type RefObject } from \"react\";\n\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"./motion.js\";\n\n/**\n * Siblings that shift because something next to them changed size (a wider month\n * name pushes the day and year along) slide to their new place instead of\n * jumping: measure where each `[data-flip]` element was, let the new layout\n * commit, put each one back where it was with a translate, then release it on a\n * transition. CSS cannot do this alone, because the layout move is not a style\n * change.\n *\n * `key` says when to look. Positions are remembered after every look and\n * refreshed when the container resizes, so a move that is not a change of `key`\n * (a window resize) does not become the next flip. Reduced motion does not flip.\n * Elements inside a leaving copy (`[data-leaving]`) are ignored.\n *\n * @param duration CSS time of the slide, e.g. `var(--duration-standard)`.\n * @param easing CSS easing of the slide, e.g. `var(--ease-enter)`.\n * @param relative Measure positions from the container's own corner instead of the\n * viewport's, so scrolling the page between two looks is not mistaken for the elements\n * moving. For lists whose container stays put (rows of a table); content that also moves\n * because its container does (centred text) wants the default.\n */\nexport function useFlip(\n container: RefObject<HTMLElement | null>,\n key: unknown,\n duration = \"var(--duration-standard)\",\n easing = \"var(--ease-enter)\",\n relative = false,\n) {\n const remembered = useRef(new Map<Element, { left: number; top: number }>());\n\n const measure = () => {\n const root = container.current;\n const next = new Map<Element, { left: number; top: number }>();\n if (!root) return next;\n const origin = relative ? root.getBoundingClientRect() : { left: 0, top: 0 };\n for (const el of root.querySelectorAll<HTMLElement>(\"[data-flip]\")) {\n if (el.closest(\"[data-leaving]\")) continue;\n const box = el.getBoundingClientRect();\n next.set(el, { left: box.left - origin.left, top: box.top - origin.top });\n }\n return next;\n };\n\n useIsomorphicLayoutEffect(() => {\n const root = container.current;\n if (!root) return;\n const before = remembered.current;\n const after = measure();\n if (before.size > 0 && !prefersReducedMotion()) {\n const moved: HTMLElement[] = [];\n for (const [el, now] of after) {\n const was = before.get(el);\n if (!was) continue;\n const dx = was.left - now.left;\n const dy = was.top - now.top;\n if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) continue;\n const node = el as HTMLElement;\n node.style.transition = \"none\";\n node.style.translate = `${dx}px ${dy}px`;\n moved.push(node);\n }\n if (moved.length > 0) {\n void root.offsetWidth;\n for (const node of moved) {\n node.style.transition = `translate ${duration} ${easing}`;\n node.style.translate = \"\";\n node.addEventListener(\n \"transitionend\",\n () => {\n node.style.transition = \"\";\n },\n { once: true },\n );\n }\n }\n }\n remembered.current = after;\n // Positions are read once per change of `key`; a resize refreshes them below.\n }, [key]);\n\n useIsomorphicLayoutEffect(() => {\n const root = container.current;\n if (!root || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n remembered.current = measure();\n });\n observer.observe(root);\n return () => observer.disconnect();\n }, []);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,QACd,WACA,KACA,WAAW,4BACX,SAAS,qBACT,WAAW,OACX;CACA,MAAM,aAAa,uBAAO,IAAI,IAA4C,CAAC;CAE3E,MAAM,gBAAgB;EACpB,MAAM,OAAO,UAAU;EACvB,MAAM,uBAAO,IAAI,IAA4C;EAC7D,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,SAAS,WAAW,KAAK,sBAAsB,IAAI;GAAE,MAAM;GAAG,KAAK;EAAE;EAC3E,KAAK,MAAM,MAAM,KAAK,iBAA8B,aAAa,GAAG;GAClE,IAAI,GAAG,QAAQ,gBAAgB,GAAG;GAClC,MAAM,MAAM,GAAG,sBAAsB;GACrC,KAAK,IAAI,IAAI;IAAE,MAAM,IAAI,OAAO,OAAO;IAAM,KAAK,IAAI,MAAM,OAAO;GAAI,CAAC;EAC1E;EACA,OAAO;CACT;CAEA,gCAAgC;EAC9B,MAAM,OAAO,UAAU;EACvB,IAAI,CAAC,MAAM;EACX,MAAM,SAAS,WAAW;EAC1B,MAAM,QAAQ,QAAQ;EACtB,IAAI,OAAO,OAAO,KAAK,CAAC,qBAAqB,GAAG;GAC9C,MAAM,QAAuB,CAAC;GAC9B,KAAK,MAAM,CAAC,IAAI,QAAQ,OAAO;IAC7B,MAAM,MAAM,OAAO,IAAI,EAAE;IACzB,IAAI,CAAC,KAAK;IACV,MAAM,KAAK,IAAI,OAAO,IAAI;IAC1B,MAAM,KAAK,IAAI,MAAM,IAAI;IACzB,IAAI,KAAK,IAAI,EAAE,IAAI,MAAO,KAAK,IAAI,EAAE,IAAI,IAAK;IAC9C,MAAM,OAAO;IACb,KAAK,MAAM,aAAa;IACxB,KAAK,MAAM,YAAY,GAAG,GAAG,KAAK,GAAG;IACrC,MAAM,KAAK,IAAI;GACjB;GACA,IAAI,MAAM,SAAS,GAAG;IACpB,KAAU;IACV,KAAK,MAAM,QAAQ,OAAO;KACxB,KAAK,MAAM,aAAa,aAAa,SAAS,GAAG;KACjD,KAAK,MAAM,YAAY;KACvB,KAAK,iBACH,uBACM;MACJ,KAAK,MAAM,aAAa;KAC1B,GACA,EAAE,MAAM,KAAK,CACf;IACF;GACF;EACF;EACA,WAAW,UAAU;CAEvB,GAAG,CAAC,GAAG,CAAC;CAER,gCAAgC;EAC9B,MAAM,OAAO,UAAU;EACvB,IAAI,CAAC,QAAQ,OAAO,mBAAmB,aAAa;EACpD,MAAM,WAAW,IAAI,qBAAqB;GACxC,WAAW,UAAU,QAAQ;EAC/B,CAAC;EACD,SAAS,QAAQ,IAAI;EACrB,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,CAAC;AACP"}
|