@seekalfred/primitives 0.6.0 → 0.7.3
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 +214 -15
- 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 +53 -4
- package/dist/alert/alert.js +100 -11
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert/index.d.ts +2 -2
- package/dist/alert/index.js +2 -2
- package/dist/alert-dialog/alert-dialog.d.ts +58 -18
- package/dist/alert-dialog/alert-dialog.js +152 -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 +1 -1
- 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/calendar/calendar.d.ts +1 -1
- package/dist/card/card.d.ts +22 -1
- package/dist/card/card.js +60 -7
- 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 +77 -0
- package/dist/carousel/carousel.js +415 -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/copy-button/copy-button.d.ts +18 -2
- package/dist/copy-button/copy-button.js +29 -8
- package/dist/copy-button/copy-button.js.map +1 -1
- 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 +86 -0
- package/dist/donut-chart/donut-chart.js +593 -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 +42 -10
- package/dist/empty-state/empty-state.js +44 -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 +50 -26
- package/dist/filter-bar/filter-bar.js +317 -157
- 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 +53 -10
- package/dist/index.js +52 -12
- package/dist/input/input.js +4 -4
- package/dist/input/input.js.map +1 -1
- 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-shell.js +10 -3
- package/dist/lib/field-shell.js.map +1 -1
- package/dist/lib/field-surface.js +31 -9
- 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/lib/use-copy-feedback.js +17 -3
- package/dist/lib/use-copy-feedback.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 +116 -0
- package/dist/line-chart/line-chart.js +740 -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 +86 -0
- package/dist/money-input/money-input.js +613 -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 +89 -0
- package/dist/morph-select/morph-select.js +712 -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 +77 -0
- package/dist/multi-select/multi-select.js +209 -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.d.ts +16 -0
- package/dist/password-field/password-field.js +39 -10
- 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/index.d.ts +2 -2
- package/dist/popover/index.js +2 -2
- package/dist/popover/popover.d.ts +51 -2
- package/dist/popover/popover.js +59 -8
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.d.ts +16 -2
- package/dist/progress/progress.js +16 -9
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/index.d.ts +2 -2
- package/dist/radio-group/index.js +2 -2
- package/dist/radio-group/radio-group.d.ts +33 -3
- package/dist/radio-group/radio-group.js +110 -10
- 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 +212 -62
- 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 +24 -2
- package/dist/search-field/search-field.js +24 -9
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +28 -14
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +22 -10
- package/dist/separator/separator.js +33 -11
- package/dist/separator/separator.js.map +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 +58 -5
- package/dist/stat-card/stat-card.js +144 -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 +55 -3
- 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.d.ts +36 -1
- package/dist/time-picker/time-picker.js +35 -7
- 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 +7 -1
- 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 +80 -0
- package/dist/usage-meter/usage-meter.js +458 -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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-area.js","names":[],"sources":["../../src/scroll-area/scroll-area.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n forwardRef,\n useEffect,\n useMemo,\n useRef,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { cnAll } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\n\n/**\n * ScrollArea. React Aria has no equivalent, so this is a plain scrolling\n * div -- and that is the honest answer rather than a shortcut.\n *\n * Census: fourteen paths match, thirteen are counted, ELEVEN render\n * anything, and across them 17 uses. Every single one passes `className`\n * and nothing else, and 13 of the 17 are a height or flex constraint plus\n * padding. `ScrollBar` is exported by the legacy and imported by nobody.\n * `orientation=\"horizontal\"` has zero call sites.\n *\n * So there is no styled-scrollbar requirement to port. The legacy pays for\n * one anyway: Radix injects a `<style>` tag that hides the native\n * scrollbar, then re-draws it in JavaScript with a ResizeObserver, a\n * portal-free overlay and a `display: table` content wrapper. That is a\n * lot of machinery to reproduce a scrollbar, and it is why the legacy\n * `ScrollArea` cannot be scrolled by keyboard at all.\n *\n * Tailwind 4.3.3 ships `scrollbar-width` and `scrollbar-color` utilities\n * (verified in the installed dist, not assumed), which gets the same look\n * in three classes and no JavaScript.\n *\n * WHAT CANNOT BE STYLED, stated rather than glossed: `scrollbar-color`\n * takes a thumb and a track colour and nothing else -- no width in pixels,\n * no radius, no hover state, no margins. `::-webkit-scrollbar` can do\n * those but is non-standard and Firefox has never supported it. This\n * component uses the standard properties only.\n */\nexport const scrollAreaVariants = cva([\n \"overflow-y-auto overscroll-contain\",\n \"box-border\",\n // Standard scrollbar properties. thumb = --color-border (5.25:1 on the\n // page), track transparent so it does not draw a channel.\n \"scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent\",\n // A scrollable region must be reachable by keyboard, or its content is\n // unreachable without a pointer -- WCAG 2.1.1, and axe's\n // scrollable-region-focusable rule. The legacy has no tabIndex at all,\n // so all 17 of its scroll areas are keyboard traps in the other\n // direction: you can see the content but never scroll to it.\n \"outline-none\",\n \"focus-visible:outline-solid\",\n \"focus-visible:outline-(length:--focus-ring-width)\",\n \"focus-visible:outline-offset-(--focus-ring-offset)\",\n \"focus-visible:outline-(--color-ring)\",\n]);\n\n/**\n * A scrolling region.\n *\n * @example\n * <ScrollArea label=\"Chat messages\" className=\"h-64\">...</ScrollArea>\n */\nexport interface ScrollAreaProps extends ComponentPropsWithoutRef<\"div\"> {\n children?: ReactNode;\n /**\n * Names the scrolling region.\n *\n * **Required**, and it took a failing test to get here. A focusable\n * element with no role fails axe `focus-order-semantics`, so the\n * keyboard fix below cannot ship without a role -- and a role with no\n * accessible name is a violation of its own. Naming it is the only\n * combination that is actually correct.\n *\n * `role=\"region\"` specifically. `group` was the first attempt -- it is\n * lighter, since region is a landmark and seventeen landmarks in one app\n * is noise -- but axe does not accept `group` as an appropriate role for\n * a focusable element, and it is right: a named region is the role the\n * ARIA practices name for a scrollable content area, and being able to\n * jump to it is the point.\n */\n label: string;\n /**\n * Fade the content out at an edge by exactly as much as there is content hidden\n * past it, up to this many pixels: the fade grows in with the first pixels of\n * scroll and is gone at the ends. `0` turns it off, and the default leaves the\n * area as it was. Written on each scroll frame straight to the element, so\n * scrolling never re-renders.\n *\n * @default 0\n */\n fade?: number;\n /**\n * Change this when the contents change, and the region scrolls back to the\n * top.\n *\n * This stays a declarative prop even though a `ref` now exists: reaching\n * for it would mean the caller writes `ref.current.scrollTop = 0` in their\n * own effect on every content change, which is worse ergonomics than a\n * prop that says \"reset when this value changes.\" That is a design choice\n * independent of whether a ref is available, not a consequence of one\n * being withheld.\n *\n * Sheet forwards its own `scrollResetKey` straight through: going a level\n * deeper in a panel and landing halfway down the new view is disorienting,\n * and it is exactly the thing every call site forgets.\n */\n scrollResetKey?: string | number;\n}\n\nexport const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(function ScrollArea(\n { className, children, label, scrollResetKey, fade = 0, onScroll, ...props },\n ref,\n) {\n const region = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n // Unconditional rather than guarded on scrollResetKey being set: with no\n // key this runs once on mount, where scrollTop is already 0.\n if (region.current) region.current.scrollTop = 0;\n }, [scrollResetKey]);\n\n // Memoized, not called inline on the div below: mergeRefs returns a new\n // callback every call, and an inline call is a new callback every render --\n // React would detach and reattach the DOM ref on every re-render instead of\n // only on mount, which matters here more than most, since ScrollArea's own\n // purpose is wrapping content that re-renders often.\n const rootRef = useMemo(() => mergeRefs(ref, region), [ref, region]);\n\n // The fade at each edge is how much content is hidden past it, up to `fade`.\n const syncFade = () => {\n const node = region.current;\n if (!node || fade <= 0) return;\n const max = Math.max(0, node.scrollHeight - node.clientHeight);\n node.style.setProperty(\"--fade-top\", `${Math.min(fade, node.scrollTop)}px`);\n node.style.setProperty(\"--fade-bottom\", `${Math.min(fade, max - node.scrollTop)}px`);\n };\n useEffect(() => {\n const node = region.current;\n if (!node || fade <= 0) return;\n syncFade();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(syncFade);\n observer.observe(node);\n for (const child of Array.from(node.children)) observer.observe(child);\n return () => observer.disconnect();\n // syncFade only reads `fade` and the node, both covered here.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fade, children]);\n\n return (\n <div\n {...props}\n ref={rootRef}\n // tabIndex 0 unconditionally: whether the content overflows is a\n // runtime fact that changes as the content does, and a region that\n // becomes scrollable after a fetch would otherwise never gain focus.\n tabIndex={0}\n role=\"region\"\n aria-label={label}\n data-slot=\"scroll-area\"\n data-fade={fade > 0 ? \"\" : undefined}\n onScroll={(event) => {\n syncFade();\n onScroll?.(event);\n }}\n className={cnAll(scrollAreaVariants(), fade > 0 && \"scroll-fade-y\", className)}\n >\n {children}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAa,qBAAqB,IAAI;CACpC;CACA;CAGA;CAMA;CACA;CACA;CACA;CACA;AACF,CAAC;AAuDD,MAAa,aAAa,WAA4C,SAAS,WAC7E,EAAE,WAAW,UAAU,OAAO,gBAAgB,OAAO,GAAG,UAAU,GAAG,SACrE,KACA;CACA,MAAM,SAAS,OAAuB,IAAI;CAE1C,gBAAgB;EAGd,IAAI,OAAO,SAAS,OAAO,QAAQ,YAAY;CACjD,GAAG,CAAC,cAAc,CAAC;CAOnB,MAAM,UAAU,cAAc,UAAU,KAAK,MAAM,GAAG,CAAC,KAAK,MAAM,CAAC;CAGnE,MAAM,iBAAiB;EACrB,MAAM,OAAO,OAAO;EACpB,IAAI,CAAC,QAAQ,QAAQ,GAAG;EACxB,MAAM,MAAM,KAAK,IAAI,GAAG,KAAK,eAAe,KAAK,YAAY;EAC7D,KAAK,MAAM,YAAY,cAAc,GAAG,KAAK,IAAI,MAAM,KAAK,SAAS,EAAE,GAAG;EAC1E,KAAK,MAAM,YAAY,iBAAiB,GAAG,KAAK,IAAI,MAAM,MAAM,KAAK,SAAS,EAAE,GAAG;CACrF;CACA,gBAAgB;EACd,MAAM,OAAO,OAAO;EACpB,IAAI,CAAC,QAAQ,QAAQ,GAAG;EACxB,SAAS;EACT,IAAI,OAAO,mBAAmB,aAAa;EAC3C,MAAM,WAAW,IAAI,eAAe,QAAQ;EAC5C,SAAS,QAAQ,IAAI;EACrB,KAAK,MAAM,SAAS,MAAM,KAAK,KAAK,QAAQ,GAAG,SAAS,QAAQ,KAAK;EACrE,aAAa,SAAS,WAAW;CAGnC,GAAG,CAAC,MAAM,QAAQ,CAAC;CAEnB,OACE,oBAAC,OAAD;EACE,GAAI;EACJ,KAAK;EAIL,UAAU;EACV,MAAK;EACL,cAAY;EACZ,aAAU;EACV,aAAW,OAAO,IAAI,KAAK,KAAA;EAC3B,WAAW,UAAU;GACnB,SAAS;GACT,WAAW,KAAK;EAClB;EACA,WAAW,MAAM,mBAAmB,GAAG,OAAO,KAAK,iBAAiB,SAAS;EAE5E;CACE,CAAA;AAET,CAAC"}
|
|
1
|
+
{"version":3,"file":"scroll-area.js","names":[],"sources":["../../src/scroll-area/scroll-area.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n type ComponentPropsWithoutRef,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from \"react\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\n\n/**\n * ScrollArea: a native scroll container with thin overlay scrollbars and edge\n * fades. React Aria has no equivalent, so the scrolling stays the browser's --\n * keyboard, touch momentum and scroll snap all behave as the platform expects --\n * and only the scrollbar is drawn here.\n *\n * Census: fourteen paths match, thirteen are counted, ELEVEN render anything,\n * and across them 17 uses. Every one passes `className` and nothing else, and 13\n * of the 17 are a height or flex constraint plus padding. The legacy Radix one\n * cannot be scrolled by keyboard at all.\n *\n * The native scrollbar is hidden and replaced by an overlay pill that appears\n * while scrolling or hovering and fades away at rest (or stays, with\n * `scrollbars=\"always\"`). It is written straight to the DOM on each scroll\n * frame, so scrolling never re-renders React. The pill can be dragged, and\n * pressing its track pages toward the pointer.\n *\n * WHERE THINGS LAND. `className`, `style` and the rest go on the ROOT, the box\n * that carries the height and the optional card. `ref` and `viewportClassName`\n * go on the VIEWPORT, the element that really scrolls -- so a caller can call\n * `ref.current.scrollTo(...)` or `scrollBy(...)` for a \"back to top\" or an\n * arrow button, and put padding on the content without moving the scrollbar\n * inside it.\n *\n * The font is declared here: the reset in @seekalfred/styles is OPT-IN, so a\n * consumer may load none, and a scroll area's content (a list of plain\n * paragraphs) would render in the browser's serif.\n */\nexport const scrollAreaRootVariants = cva(\n [\"scroll-area relative flex min-h-0 min-w-0 flex-col overflow-hidden box-border font-sans\"],\n {\n variants: {\n /** The area is a white card: the panel radius and a hairline border. */\n framed: {\n true: \"rounded-panel border-(length:--border-width) border-border bg-surface\",\n false: \"\",\n },\n },\n defaultVariants: { framed: false },\n },\n);\n\nexport const scrollAreaVariants = cva(\n [\n \"scroll-area-viewport min-h-0 min-w-0 flex-1 overscroll-contain box-border\",\n // A scrollable region must be reachable by keyboard, or its content is\n // unreachable without a pointer -- WCAG 2.1.1, and axe's\n // scrollable-region-focusable rule. The legacy has no tabIndex at all.\n \"outline-none\",\n // Inset, not offset outward: the root is overflow-hidden (it clips the\n // overlay scrollbars, the fades and the framed radius), so a ring drawn\n // outside the viewport was cut off on every side and keyboard focus was\n // invisible -- WCAG 2.4.7. Drawn just inside the edge, nothing around the\n // area can clip it, and a caller can still recolour it through\n // `viewportClassName`. The radius is inherited so the ring follows the\n // framed card's corners.\n //\n // With `fade` on (data-fade) the viewport carries the edge-fade mask, which\n // would fade the ring's top and bottom out with the content mid-scroll. The\n // ring's style stays `none` there and the ROOT paints it instead, on an\n // unmasked overlay (`scroll-area` in styles.css).\n \"focus-visible:not-data-[fade]:outline-solid\",\n \"focus-visible:outline-(length:--focus-ring-width)\",\n \"focus-visible:-outline-offset-(--focus-ring-width)\",\n \"focus-visible:outline-(--color-ring)\",\n \"rounded-[inherit]\",\n ],\n {\n variants: {\n orientation: {\n vertical: \"overflow-x-hidden overflow-y-auto\",\n horizontal: \"overflow-x-auto overflow-y-hidden\",\n both: \"overflow-auto\",\n },\n },\n defaultVariants: { orientation: \"vertical\" },\n },\n);\n\ntype Axis = \"y\" | \"x\";\n\ninterface Drag {\n axis: Axis;\n start: number;\n scroll: number;\n ratio: number;\n sign: number;\n}\n\n/** The shortest a scrollbar thumb gets, so a very long list still leaves something to grab. */\nconst MIN_THUMB = 28;\n\n/**\n * A scrolling region.\n *\n * @example\n * <ScrollArea label=\"Activity\" className=\"h-64\" framed fade={28}>...</ScrollArea>\n */\nexport interface ScrollAreaProps extends ComponentPropsWithoutRef<\"div\"> {\n children?: ReactNode;\n /**\n * Names the scrolling region.\n *\n * **Required**, and it took a failing test to get here. A focusable\n * element with no role fails axe `focus-order-semantics`, so the\n * keyboard fix cannot ship without a role -- and a role with no\n * accessible name is a violation of its own. Naming it is the only\n * combination that is actually correct.\n *\n * `role=\"region\"` specifically. `group` was the first attempt -- it is\n * lighter, since region is a landmark and seventeen landmarks in one app\n * is noise -- but axe does not accept `group` as an appropriate role for\n * a focusable element, and it is right: a named region is the role the\n * ARIA practices name for a scrollable content area, and being able to\n * jump to it is the point.\n */\n label: string;\n /**\n * Which axes scroll.\n *\n * @default \"vertical\"\n */\n orientation?: \"vertical\" | \"horizontal\" | \"both\";\n /**\n * Fade the content out at an edge by exactly as much as there is content hidden\n * past it, up to this many pixels: the fade grows in with the first pixels of\n * scroll and is gone at the ends, so an edge fades only while content\n * overflows past it. `0` turns it off, and the default leaves the area as it\n * was. Written on each scroll frame straight to the element, so scrolling\n * never re-renders.\n *\n * @default 0\n */\n fade?: number;\n /**\n * `auto` shows the scrollbar while scrolling or hovering and fades it away at\n * rest; `always` keeps it visible whenever the content overflows.\n *\n * @default \"auto\"\n */\n scrollbars?: \"auto\" | \"always\";\n /**\n * How long the scrollbar lingers after scrolling stops, in ms.\n *\n * @default 900\n */\n hideDelay?: number;\n /**\n * Draws the area as a white card: the panel radius and a light border. The\n * content is clipped to it, so an edge fade and the scrollbar sit inside.\n *\n * @default false\n */\n framed?: boolean;\n /**\n * Classes for the viewport, the element that scrolls. Padding for the\n * content goes here.\n */\n viewportClassName?: string;\n /**\n * Change this when the contents change, and the region scrolls back to the\n * top.\n *\n * This stays a declarative prop even though a `ref` exists: reaching\n * for it would mean the caller writes `ref.current.scrollTop = 0` in their\n * own effect on every content change, which is worse ergonomics than a\n * prop that says \"reset when this value changes.\"\n *\n * Sheet forwards its own `scrollResetKey` straight through: going a level\n * deeper in a panel and landing halfway down the new view is disorienting,\n * and it is exactly the thing every call site forgets.\n */\n scrollResetKey?: string | number;\n}\n\n/** The `ref` reaches the VIEWPORT, the element that scrolls. `onScroll` runs on its scroll events. */\nexport const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(function ScrollArea(\n {\n className,\n children,\n label,\n scrollResetKey,\n orientation = \"vertical\",\n fade = 0,\n scrollbars = \"auto\",\n hideDelay = 900,\n framed = false,\n viewportClassName,\n onScroll,\n ...props\n },\n ref,\n) {\n const root = useRef<HTMLDivElement>(null);\n const viewport = useRef<HTMLDivElement>(null);\n const tracks = useRef<Record<Axis, HTMLDivElement | null>>({ x: null, y: null });\n const thumbs = useRef<Record<Axis, HTMLDivElement | null>>({ x: null, y: null });\n const hideTimer = useRef<number | undefined>(undefined);\n const drag = useRef<Drag | null>(null);\n\n const vertical = orientation !== \"horizontal\";\n const horizontal = orientation !== \"vertical\";\n\n useEffect(() => {\n // Unconditional rather than guarded on scrollResetKey being set: with no\n // key this runs once on mount, where scrollTop is already 0.\n if (viewport.current) viewport.current.scrollTop = 0;\n }, [scrollResetKey]);\n\n // Memoized, not called inline: mergeRefs returns a new callback every call, so\n // an inline call would detach and reattach the DOM ref on every re-render.\n const viewportRef = useMemo(() => mergeRefs(ref, viewport), [ref]);\n\n /*\n * Everything visual is written straight to the DOM: the fade at each edge is\n * how much content is hidden past it (up to `fade`), and a thumb is sized and\n * placed from the scroll position. A track with nothing to scroll is hidden.\n */\n const sync = useCallback(() => {\n const node = viewport.current;\n if (!node) return;\n const { scrollTop, scrollHeight, clientHeight, scrollWidth, clientWidth } = node;\n const maxY = Math.max(0, scrollHeight - clientHeight);\n const maxX = Math.max(0, scrollWidth - clientWidth);\n // scrollLeft runs negative in a right-to-left layout; the distance travelled is what counts.\n const left = Math.abs(node.scrollLeft);\n\n if (fade > 0) {\n node.style.setProperty(\"--fade-top\", `${vertical ? Math.min(fade, scrollTop) : 0}px`);\n node.style.setProperty(\n \"--fade-bottom\",\n `${vertical ? Math.min(fade, Math.max(0, maxY - scrollTop)) : 0}px`,\n );\n node.style.setProperty(\"--fade-left\", `${horizontal ? Math.min(fade, left) : 0}px`);\n node.style.setProperty(\n \"--fade-right\",\n `${horizontal ? Math.min(fade, Math.max(0, maxX - left)) : 0}px`,\n );\n }\n\n const place = (axis: Axis, scroll: number, max: number, client: number, total: number) => {\n const track = tracks.current[axis];\n const thumb = thumbs.current[axis];\n if (!track || !thumb) return;\n const overflowing = max > 0.5;\n track.toggleAttribute(\"data-hidden\", !overflowing);\n if (!overflowing) return;\n const length = axis === \"y\" ? track.clientHeight : track.clientWidth;\n const size = Math.max(MIN_THUMB, (length * client) / total);\n const offset = (length - size) * (scroll / max);\n const sign = axis === \"x\" && getComputedStyle(node).direction === \"rtl\" ? -1 : 1;\n thumb.style[axis === \"y\" ? \"height\" : \"width\"] = `${size}px`;\n thumb.style.transform =\n axis === \"y\" ? `translate3d(0, ${offset}px, 0)` : `translate3d(${offset * sign}px, 0, 0)`;\n };\n if (vertical) place(\"y\", scrollTop, maxY, clientHeight, scrollHeight);\n if (horizontal) place(\"x\", left, maxX, clientWidth, scrollWidth);\n root.current?.toggleAttribute(\"data-overflow\", maxY > 0.5 || maxX > 0.5);\n }, [fade, horizontal, vertical]);\n\n useIsomorphicLayoutEffect(() => {\n const node = viewport.current;\n if (!node) return;\n sync();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => sync());\n observer.observe(node);\n for (const child of Array.from(node.children)) observer.observe(child);\n return () => observer.disconnect();\n // `children` re-observes when the content's own boxes are replaced.\n }, [sync, children]);\n\n useEffect(() => () => window.clearTimeout(hideTimer.current), []);\n\n const wake = () => {\n const el = root.current;\n if (!el) return;\n el.setAttribute(\"data-scrolling\", \"\");\n window.clearTimeout(hideTimer.current);\n hideTimer.current = window.setTimeout(() => el.removeAttribute(\"data-scrolling\"), hideDelay);\n };\n\n /* Dragging a thumb maps pointer travel to scroll distance. */\n const onThumbDown = (event: ReactPointerEvent<HTMLDivElement>, axis: Axis) => {\n const node = viewport.current;\n const track = tracks.current[axis];\n const thumb = thumbs.current[axis];\n if (!node || !track || !thumb || event.button !== 0) return;\n event.preventDefault();\n event.stopPropagation();\n event.currentTarget.setPointerCapture(event.pointerId);\n const length = axis === \"y\" ? track.clientHeight : track.clientWidth;\n const size = axis === \"y\" ? thumb.offsetHeight : thumb.offsetWidth;\n const max =\n axis === \"y\" ? node.scrollHeight - node.clientHeight : node.scrollWidth - node.clientWidth;\n drag.current = {\n axis,\n start: axis === \"y\" ? event.clientY : event.clientX,\n scroll: axis === \"y\" ? node.scrollTop : node.scrollLeft,\n ratio: max / Math.max(1, length - size),\n sign: axis === \"x\" && getComputedStyle(node).direction === \"rtl\" ? -1 : 1,\n };\n root.current?.setAttribute(\"data-dragging\", axis);\n };\n const onThumbMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n const state = drag.current;\n const node = viewport.current;\n if (!state || !node) return;\n const travel = (state.axis === \"y\" ? event.clientY : event.clientX) - state.start;\n const to = state.scroll + travel * state.ratio * state.sign;\n if (state.axis === \"y\") node.scrollTop = to;\n else node.scrollLeft = to;\n };\n const onThumbUp = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (!drag.current) return;\n drag.current = null;\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n root.current?.removeAttribute(\"data-dragging\");\n wake();\n };\n /* Pressing the track pages toward the pointer. */\n const onTrackDown = (event: ReactPointerEvent<HTMLDivElement>, axis: Axis) => {\n const node = viewport.current;\n const thumb = thumbs.current[axis];\n if (!node || !thumb || event.button !== 0 || event.target !== event.currentTarget) return;\n const box = thumb.getBoundingClientRect();\n const before = axis === \"y\" ? event.clientY < box.top : event.clientX < box.left;\n const page = (axis === \"y\" ? node.clientHeight : node.clientWidth) * 0.9 * (before ? -1 : 1);\n node.scrollBy({\n [axis === \"y\" ? \"top\" : \"left\"]: page,\n behavior: prefersReducedMotion() ? \"auto\" : \"smooth\",\n });\n };\n\n const bar = (axis: Axis) => (\n <div\n ref={(node) => {\n tracks.current[axis] = node;\n }}\n data-slot=\"scroll-area-track\"\n data-axis={axis}\n data-hidden=\"\"\n aria-hidden=\"true\"\n onPointerDown={(event) => onTrackDown(event, axis)}\n >\n <div\n ref={(node) => {\n thumbs.current[axis] = node;\n }}\n data-slot=\"scroll-area-thumb\"\n onPointerDown={(event) => onThumbDown(event, axis)}\n onPointerMove={onThumbMove}\n onPointerUp={onThumbUp}\n onPointerCancel={onThumbUp}\n />\n </div>\n );\n\n const fadeClass =\n orientation === \"vertical\"\n ? \"scroll-fade-y\"\n : orientation === \"horizontal\"\n ? \"scroll-fade-x\"\n : \"scroll-fade-both\";\n\n return (\n <div\n {...props}\n ref={root}\n data-slot=\"scroll-area-root\"\n data-orientation={orientation}\n data-scrollbars={scrollbars}\n className={cn(scrollAreaRootVariants({ framed }), className)}\n >\n <div\n ref={viewportRef}\n // tabIndex 0 unconditionally: whether the content overflows is a\n // runtime fact that changes as the content does, and a region that\n // becomes scrollable after a fetch would otherwise never gain focus.\n tabIndex={0}\n role=\"region\"\n aria-label={label}\n data-slot=\"scroll-area\"\n data-fade={fade > 0 ? \"\" : undefined}\n onScroll={(event) => {\n sync();\n wake();\n onScroll?.(event);\n }}\n className={cn(\n cnAll(scrollAreaVariants({ orientation }), fade > 0 && fadeClass),\n viewportClassName,\n )}\n >\n {children}\n </div>\n {vertical ? bar(\"y\") : null}\n {horizontal ? bar(\"x\") : null}\n </div>\n );\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/scrub-input/evaluate.d.ts
|
|
2
|
+
type Parsed = {
|
|
3
|
+
value: number;
|
|
4
|
+
unit?: string;
|
|
5
|
+
} | null;
|
|
6
|
+
/**
|
|
7
|
+
* Evaluates + - * / and parentheses over numbers that may carry a unit, such as
|
|
8
|
+
* `(100-24)/2` or `50%`. Commas read as decimal points, and `x` or the multiplication
|
|
9
|
+
* sign multiply. Returns `null` for anything else, or for a unit that is not in
|
|
10
|
+
* `units` (`deg` and the degree sign are aliases of each other).
|
|
11
|
+
*/
|
|
12
|
+
export declare function evaluateScrubExpression(text: string, units: readonly string[]): Parsed;
|
|
13
|
+
//#endregion
|
|
14
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//#region src/scrub-input/evaluate.ts
|
|
2
|
+
/**
|
|
3
|
+
* Evaluates + - * / and parentheses over numbers that may carry a unit, such as
|
|
4
|
+
* `(100-24)/2` or `50%`. Commas read as decimal points, and `x` or the multiplication
|
|
5
|
+
* sign multiply. Returns `null` for anything else, or for a unit that is not in
|
|
6
|
+
* `units` (`deg` and the degree sign are aliases of each other).
|
|
7
|
+
*/
|
|
8
|
+
function evaluateScrubExpression(text, units) {
|
|
9
|
+
const source = text.trim().replace(/,/g, ".").replace(/[×x](?=\s*[\d(.])/gi, "*").replace(/÷/g, "/").replace(/−/g, "-");
|
|
10
|
+
if (!source) return null;
|
|
11
|
+
const tokens = source.match(/\d*\.?\d+(?:e[+-]?\d+)?|[a-z%°]+|[-+*/()]|\S/gi);
|
|
12
|
+
if (!tokens) return null;
|
|
13
|
+
const list = tokens;
|
|
14
|
+
let index = 0;
|
|
15
|
+
let unit;
|
|
16
|
+
let bad = false;
|
|
17
|
+
const known = units.map((entry) => entry.toLowerCase());
|
|
18
|
+
const peek = () => list[index];
|
|
19
|
+
function primary() {
|
|
20
|
+
const token = list[index++];
|
|
21
|
+
if (token === "(") {
|
|
22
|
+
const inner = sum();
|
|
23
|
+
if (list[index++] !== ")") bad = true;
|
|
24
|
+
return inner;
|
|
25
|
+
}
|
|
26
|
+
if (token === "-") return -primary();
|
|
27
|
+
if (token === "+") return primary();
|
|
28
|
+
if (token === void 0 || !/^\d*\.?\d+/.test(token)) {
|
|
29
|
+
bad = true;
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
const number = Number(token);
|
|
33
|
+
const next = peek();
|
|
34
|
+
if (next && /^[a-z%°]+$/i.test(next)) {
|
|
35
|
+
index++;
|
|
36
|
+
const word = next.toLowerCase();
|
|
37
|
+
const alias = word === "°" ? "deg" : word === "deg" ? "°" : word;
|
|
38
|
+
const found = known.includes(word) ? known.indexOf(word) : known.indexOf(alias);
|
|
39
|
+
if (found < 0) bad = true;
|
|
40
|
+
else unit = units[found];
|
|
41
|
+
}
|
|
42
|
+
return number;
|
|
43
|
+
}
|
|
44
|
+
function product() {
|
|
45
|
+
let left = primary();
|
|
46
|
+
while (peek() === "*" || peek() === "/") {
|
|
47
|
+
const op = list[index++];
|
|
48
|
+
const right = primary();
|
|
49
|
+
left = op === "*" ? left * right : left / right;
|
|
50
|
+
}
|
|
51
|
+
return left;
|
|
52
|
+
}
|
|
53
|
+
function sum() {
|
|
54
|
+
let left = product();
|
|
55
|
+
while (peek() === "+" || peek() === "-") {
|
|
56
|
+
const op = list[index++];
|
|
57
|
+
const right = product();
|
|
58
|
+
left = op === "+" ? left + right : left - right;
|
|
59
|
+
}
|
|
60
|
+
return left;
|
|
61
|
+
}
|
|
62
|
+
const value = sum();
|
|
63
|
+
if (bad || index !== list.length || !Number.isFinite(value)) return null;
|
|
64
|
+
return {
|
|
65
|
+
value,
|
|
66
|
+
unit
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { evaluateScrubExpression };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.js","names":[],"sources":["../../src/scrub-input/evaluate.ts"],"sourcesContent":["type Parsed = { value: number; unit?: string } | null;\n\n/**\n * Evaluates + - * / and parentheses over numbers that may carry a unit, such as\n * `(100-24)/2` or `50%`. Commas read as decimal points, and `x` or the multiplication\n * sign multiply. Returns `null` for anything else, or for a unit that is not in\n * `units` (`deg` and the degree sign are aliases of each other).\n */\nexport function evaluateScrubExpression(text: string, units: readonly string[]): Parsed {\n const source = text\n .trim()\n .replace(/,/g, \".\")\n .replace(/[×x](?=\\s*[\\d(.])/gi, \"*\")\n .replace(/÷/g, \"/\")\n .replace(/−/g, \"-\");\n if (!source) return null;\n const tokens = source.match(/\\d*\\.?\\d+(?:e[+-]?\\d+)?|[a-z%°]+|[-+*/()]|\\S/gi);\n if (!tokens) return null;\n const list = tokens;\n let index = 0;\n let unit: string | undefined;\n let bad = false;\n const known = units.map((entry) => entry.toLowerCase());\n const peek = () => list[index];\n\n function primary(): number {\n const token = list[index++];\n if (token === \"(\") {\n const inner = sum();\n if (list[index++] !== \")\") bad = true;\n return inner;\n }\n if (token === \"-\") return -primary();\n if (token === \"+\") return primary();\n if (token === undefined || !/^\\d*\\.?\\d+/.test(token)) {\n bad = true;\n return 0;\n }\n const number = Number(token);\n const next = peek();\n if (next && /^[a-z%°]+$/i.test(next)) {\n index++;\n const word = next.toLowerCase();\n const alias = word === \"°\" ? \"deg\" : word === \"deg\" ? \"°\" : word;\n const found = known.includes(word) ? known.indexOf(word) : known.indexOf(alias);\n if (found < 0) bad = true;\n else unit = units[found];\n }\n return number;\n }\n function product(): number {\n let left = primary();\n while (peek() === \"*\" || peek() === \"/\") {\n const op = list[index++];\n const right = primary();\n left = op === \"*\" ? left * right : left / right;\n }\n return left;\n }\n function sum(): number {\n let left = product();\n while (peek() === \"+\" || peek() === \"-\") {\n const op = list[index++];\n const right = product();\n left = op === \"+\" ? left + right : left - right;\n }\n return left;\n }\n\n const value = sum();\n if (bad || index !== list.length || !Number.isFinite(value)) return null;\n return { value, unit };\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,wBAAwB,MAAc,OAAkC;CACtF,MAAM,SAAS,KACZ,KAAK,CAAC,CACN,QAAQ,MAAM,GAAG,CAAC,CAClB,QAAQ,uBAAuB,GAAG,CAAC,CACnC,QAAQ,MAAM,GAAG,CAAC,CAClB,QAAQ,MAAM,GAAG;CACpB,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,SAAS,OAAO,MAAM,gDAAgD;CAC5E,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,OAAO;CACb,IAAI,QAAQ;CACZ,IAAI;CACJ,IAAI,MAAM;CACV,MAAM,QAAQ,MAAM,KAAK,UAAU,MAAM,YAAY,CAAC;CACtD,MAAM,aAAa,KAAK;CAExB,SAAS,UAAkB;EACzB,MAAM,QAAQ,KAAK;EACnB,IAAI,UAAU,KAAK;GACjB,MAAM,QAAQ,IAAI;GAClB,IAAI,KAAK,aAAa,KAAK,MAAM;GACjC,OAAO;EACT;EACA,IAAI,UAAU,KAAK,OAAO,CAAC,QAAQ;EACnC,IAAI,UAAU,KAAK,OAAO,QAAQ;EAClC,IAAI,UAAU,KAAA,KAAa,CAAC,aAAa,KAAK,KAAK,GAAG;GACpD,MAAM;GACN,OAAO;EACT;EACA,MAAM,SAAS,OAAO,KAAK;EAC3B,MAAM,OAAO,KAAK;EAClB,IAAI,QAAQ,cAAc,KAAK,IAAI,GAAG;GACpC;GACA,MAAM,OAAO,KAAK,YAAY;GAC9B,MAAM,QAAQ,SAAS,MAAM,QAAQ,SAAS,QAAQ,MAAM;GAC5D,MAAM,QAAQ,MAAM,SAAS,IAAI,IAAI,MAAM,QAAQ,IAAI,IAAI,MAAM,QAAQ,KAAK;GAC9E,IAAI,QAAQ,GAAG,MAAM;QAChB,OAAO,MAAM;EACpB;EACA,OAAO;CACT;CACA,SAAS,UAAkB;EACzB,IAAI,OAAO,QAAQ;EACnB,OAAO,KAAK,MAAM,OAAO,KAAK,MAAM,KAAK;GACvC,MAAM,KAAK,KAAK;GAChB,MAAM,QAAQ,QAAQ;GACtB,OAAO,OAAO,MAAM,OAAO,QAAQ,OAAO;EAC5C;EACA,OAAO;CACT;CACA,SAAS,MAAc;EACrB,IAAI,OAAO,QAAQ;EACnB,OAAO,KAAK,MAAM,OAAO,KAAK,MAAM,KAAK;GACvC,MAAM,KAAK,KAAK;GAChB,MAAM,QAAQ,QAAQ;GACtB,OAAO,OAAO,MAAM,OAAO,QAAQ,OAAO;EAC5C;EACA,OAAO;CACT;CAEA,MAAM,QAAQ,IAAI;CAClB,IAAI,OAAO,UAAU,KAAK,UAAU,CAAC,OAAO,SAAS,KAAK,GAAG,OAAO;CACpE,OAAO;EAAE;EAAO;CAAK;AACvB"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ScrubInput, ScrubInputChangeDetails, ScrubInputProps, ScrubInputSource } from "./scrub-input.js";
|
|
2
|
+
import { evaluateScrubExpression } from "./evaluate.js";
|
|
3
|
+
export { ScrubInput, type ScrubInputChangeDetails, type ScrubInputProps, type ScrubInputSource, evaluateScrubExpression };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { InputHTMLAttributes, ReactNode } from "react";
|
|
2
|
+
//#region src/scrub-input/scrub-input.d.ts
|
|
3
|
+
export type ScrubInputSource = "scrub" | "type" | "key" | "reset";
|
|
4
|
+
export interface ScrubInputChangeDetails {
|
|
5
|
+
/** What produced the value: a label drag, a typed entry, an arrow key, or a double click reset. */
|
|
6
|
+
source: ScrubInputSource;
|
|
7
|
+
/** The unit in effect after the change. */
|
|
8
|
+
unit: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ScrubInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "type" | "size" | "min" | "max" | "step" | "prefix" | "className"> {
|
|
11
|
+
/** Text or a 16px icon. It is the scrub handle. An icon label needs `aria-label` so the field keeps an accessible name. */
|
|
12
|
+
label: ReactNode;
|
|
13
|
+
/** The value. Controlled when given. */
|
|
14
|
+
value?: number;
|
|
15
|
+
/** @default 0 */
|
|
16
|
+
defaultValue?: number;
|
|
17
|
+
/** Fires for every change, including each step of a scrub. */
|
|
18
|
+
onValueChange?: (value: number, details: ScrubInputChangeDetails) => void;
|
|
19
|
+
/** Fires once when a change settles: a scrub ends, Enter or blur commits typing, or a key step lands. Save on this. */
|
|
20
|
+
onValueCommit?: (value: number, details: ScrubInputChangeDetails) => void;
|
|
21
|
+
/** @default -Infinity */
|
|
22
|
+
min?: number;
|
|
23
|
+
/** @default Infinity */
|
|
24
|
+
max?: number;
|
|
25
|
+
/** One step for the arrow keys and for scrubbing. @default 1 */
|
|
26
|
+
step?: number;
|
|
27
|
+
/** The step with Alt held. @default step / 10 */
|
|
28
|
+
fineStep?: number;
|
|
29
|
+
/** The step with Shift held; coarse steps land on the coarse grid. @default step * 10 */
|
|
30
|
+
coarseStep?: number;
|
|
31
|
+
/** Decimal places kept. Defaults to the precision of `step` or `fineStep`. */
|
|
32
|
+
precision?: number;
|
|
33
|
+
/** Pointer travel in px for one step while scrubbing. @default 2 */
|
|
34
|
+
pixelsPerStep?: number;
|
|
35
|
+
/** Wraps from `max` back to `min`, as a rotation does. Needs both limits. @default false */
|
|
36
|
+
wrap?: boolean;
|
|
37
|
+
/** The unit shown after the number. Controlled when given. With more than one entry in `units` it is a button that cycles them. */
|
|
38
|
+
unit?: string;
|
|
39
|
+
defaultUnit?: string;
|
|
40
|
+
/** The units a typed value may carry, such as `["px", "%"]`. Defaults to the current unit. */
|
|
41
|
+
units?: readonly string[];
|
|
42
|
+
onUnitChange?: (unit: string) => void;
|
|
43
|
+
/** Double click the label to return here. */
|
|
44
|
+
resetValue?: number;
|
|
45
|
+
/** Shows where the value sits between `min` and `max` as a quiet fill. On by default when both limits are set. */
|
|
46
|
+
track?: boolean;
|
|
47
|
+
/** `md` is the compact control height, `sm` is 30px. @default "md" */
|
|
48
|
+
size?: "sm" | "md";
|
|
49
|
+
/** Locks the mouse pointer while it scrubs, when the browser allows it, so a long drag never runs out of screen. @default true */
|
|
50
|
+
pointerLock?: boolean;
|
|
51
|
+
/** Extra classes for the field. Merged last, so `w-40` sizes it. */
|
|
52
|
+
className?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A compact number field for inspectors and property panels. Drag the label sideways to
|
|
56
|
+
* scrub the value (the mouse pointer is locked when the browser allows it, so a long drag
|
|
57
|
+
* never runs out of screen), or click in and type an exact value. A typed entry takes
|
|
58
|
+
* arithmetic such as `12*2` or `(100-24)/2`, and a unit from `units` such as `50%`.
|
|
59
|
+
*
|
|
60
|
+
* Keyboard: the arrow keys step (Shift ten times coarser, Alt ten times finer), Enter
|
|
61
|
+
* commits typing, Escape restores the last value (or cancels a scrub back to where it
|
|
62
|
+
* began). A double click on the label restores `resetValue`. The input is a spinbutton, and
|
|
63
|
+
* its value text spells out percent and degrees.
|
|
64
|
+
*
|
|
65
|
+
* At a limit the field strains: dragged past `min` or `max` it stretches against
|
|
66
|
+
* rubber-band resistance and springs back on release, and a refused step or an invalid
|
|
67
|
+
* entry kicks the number. Reduced motion removes the strain, the kick and the fill's glide.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* <ScrubInput label="W" value={width} onValueChange={setWidth} min={0} units={["px", "%"]} />
|
|
71
|
+
*/
|
|
72
|
+
export declare const ScrubInput: import("react").ForwardRefExoticComponent<ScrubInputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
73
|
+
//#endregion
|
|
74
|
+
//# sourceMappingURL=scrub-input.d.ts.map
|