@eifi1/ui-kit 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +32 -0
- package/README.md +498 -57
- package/dist/chart.d.ts +10 -0
- package/dist/chart.js +10 -0
- package/dist/chart.js.map +1 -0
- package/dist/components/account-settings.d.ts +11 -0
- package/dist/components/account-settings.js +19 -12
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/alert-banner.d.ts +10 -6
- package/dist/components/alert-banner.js +20 -16
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +30 -4
- package/dist/components/amount-input.js +36 -14
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/calculator.d.ts +24 -7
- package/dist/components/calculator.js +37 -20
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/chart-zoom.d.ts +156 -0
- package/dist/components/chart-zoom.js +262 -0
- package/dist/components/chart-zoom.js.map +1 -0
- package/dist/components/chart.d.ts +28 -3
- package/dist/components/chart.js +71 -32
- package/dist/components/chart.js.map +1 -1
- package/dist/components/checkbox.d.ts +51 -0
- package/dist/components/checkbox.js +123 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/chip.d.ts +128 -0
- package/dist/components/chip.js +357 -0
- package/dist/components/chip.js.map +1 -0
- package/dist/components/choice-parts.d.ts +24 -0
- package/dist/components/choice-parts.js +18 -0
- package/dist/components/choice-parts.js.map +1 -0
- package/dist/components/combobox-core.d.ts +52 -9
- package/dist/components/combobox-core.js +91 -20
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +43 -33
- package/dist/components/combobox.js +409 -340
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/currency-select.d.ts +48 -8
- package/dist/components/currency-select.js +107 -65
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +15 -3
- package/dist/components/data-table-filter-popover.js +23 -17
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-filters.js +3 -2
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +53 -2
- package/dist/components/data-table-labels.js +31 -2
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-pagination.d.ts +3 -1
- package/dist/components/data-table-pagination.js +16 -10
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-sort.js +1 -0
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +201 -85
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +25 -9
- package/dist/components/date-picker.js +232 -94
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/dropdown.d.ts +96 -20
- package/dist/components/dropdown.js +77 -33
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +5 -4
- package/dist/components/entity-combobox.js +107 -76
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +36 -0
- package/dist/components/facing-pair.js +40 -0
- package/dist/components/facing-pair.js.map +1 -0
- package/dist/components/field-sync.d.ts +164 -0
- package/dist/components/field-sync.js +264 -0
- package/dist/components/field-sync.js.map +1 -0
- package/dist/components/file-dropzone.d.ts +38 -6
- package/dist/components/file-dropzone.js +34 -9
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/full-bleed-dialog.d.ts +37 -26
- package/dist/components/full-bleed-dialog.js +20 -5
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/grouped-picker.d.ts +21 -10
- package/dist/components/grouped-picker.js +66 -52
- package/dist/components/grouped-picker.js.map +1 -1
- package/dist/components/hover-menu.d.ts +20 -4
- package/dist/components/hover-menu.js +15 -4
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +102 -14
- package/dist/components/mini-calendar.js +256 -70
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +9 -3
- package/dist/components/modal.js +34 -45
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +79 -0
- package/dist/components/month-picker.js +342 -0
- package/dist/components/month-picker.js.map +1 -0
- package/dist/components/multi-entity-combobox.d.ts +7 -6
- package/dist/components/multi-entity-combobox.js +112 -72
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.d.ts +13 -11
- package/dist/components/multi-select.js +161 -84
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-field.d.ts +92 -0
- package/dist/components/number-field.js +111 -0
- package/dist/components/number-field.js.map +1 -0
- package/dist/components/number-input.d.ts +28 -2
- package/dist/components/number-input.js +12 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +32 -4
- package/dist/components/numpad-sheet.js +30 -13
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/page-contents.d.ts +90 -0
- package/dist/components/page-contents.js +170 -0
- package/dist/components/page-contents.js.map +1 -0
- package/dist/components/password-strength.d.ts +106 -0
- package/dist/components/password-strength.js +132 -0
- package/dist/components/password-strength.js.map +1 -0
- package/dist/components/picker-sheet.d.ts +23 -15
- package/dist/components/picker-sheet.js +34 -14
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/popover.d.ts +62 -4
- package/dist/components/popover.js +52 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/search-field.d.ts +25 -7
- package/dist/components/search-field.js +7 -5
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +14 -0
- package/dist/components/series-chart-labels.js +11 -0
- package/dist/components/series-chart-labels.js.map +1 -0
- package/dist/components/series-chart.d.ts +207 -0
- package/dist/components/series-chart.js +223 -0
- package/dist/components/series-chart.js.map +1 -0
- package/dist/components/settings-fields.d.ts +14 -4
- package/dist/components/settings-fields.js +19 -8
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/signature-pad.d.ts +119 -0
- package/dist/components/signature-pad.js +405 -0
- package/dist/components/signature-pad.js.map +1 -0
- package/dist/components/slider.d.ts +80 -0
- package/dist/components/slider.js +155 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/sparkline.d.ts +93 -0
- package/dist/components/sparkline.js +205 -0
- package/dist/components/sparkline.js.map +1 -0
- package/dist/components/stat-tile.d.ts +151 -0
- package/dist/components/stat-tile.js +236 -0
- package/dist/components/stat-tile.js.map +1 -0
- package/dist/components/swipeable-row.d.ts +25 -2
- package/dist/components/swipeable-row.js +31 -0
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/switch.d.ts +60 -0
- package/dist/components/switch.js +81 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/time-input.d.ts +58 -0
- package/dist/components/time-input.js +50 -0
- package/dist/components/time-input.js.map +1 -0
- package/dist/components/toggle-group.d.ts +18 -3
- package/dist/components/toggle-group.js +10 -6
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +115 -0
- package/dist/components/toggle-legend.js +114 -0
- package/dist/components/toggle-legend.js.map +1 -0
- package/dist/components/tooltip.d.ts +40 -11
- package/dist/components/tooltip.js +77 -20
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/treemap.d.ts +132 -0
- package/dist/components/treemap.js +179 -0
- package/dist/components/treemap.js.map +1 -0
- package/dist/components/ui.d.ts +159 -46
- package/dist/components/ui.js +220 -115
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-mobile-reveal.js +1 -0
- package/dist/components/use-mobile-reveal.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/components/use-table-state.js +38 -17
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.d.ts +15 -4
- package/dist/components/user-avatar.js +5 -4
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/components/wizard-stepper.js +3 -2
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
- package/dist/data-table.d.ts +7 -0
- package/dist/data-table.js +12 -0
- package/dist/data-table.js.map +1 -0
- package/dist/feedback/feedback-attachment.js +12 -11
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.js +2 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +33 -7
- package/dist/feedback/feedback-inbox.js +49 -37
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback.d.ts +5 -0
- package/dist/feedback.js +5 -0
- package/dist/feedback.js.map +1 -0
- package/dist/hooks/use-anchored-panel.js +1 -0
- package/dist/hooks/use-anchored-panel.js.map +1 -1
- package/dist/hooks/use-anchored-rect.js +1 -0
- package/dist/hooks/use-anchored-rect.js.map +1 -1
- package/dist/hooks/use-announce.d.ts +46 -0
- package/dist/hooks/use-announce.js +37 -0
- package/dist/hooks/use-announce.js.map +1 -0
- package/dist/hooks/use-body-scroll-lock.js +1 -0
- package/dist/hooks/use-body-scroll-lock.js.map +1 -1
- package/dist/hooks/use-close-transition.js +1 -0
- package/dist/hooks/use-close-transition.js.map +1 -1
- package/dist/hooks/use-dismiss.js +1 -0
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +16 -0
- package/dist/hooks/use-focus-trap.js +67 -0
- package/dist/hooks/use-focus-trap.js.map +1 -0
- package/dist/hooks/use-media-query.js +1 -0
- package/dist/hooks/use-media-query.js.map +1 -1
- package/dist/hooks/use-overlay-history.js +27 -1
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/hooks/use-row-swipe.js +1 -0
- package/dist/hooks/use-row-swipe.js.map +1 -1
- package/dist/i18n/defaults.d.ts +31 -0
- package/dist/i18n/defaults.js +63 -0
- package/dist/i18n/defaults.js.map +1 -0
- package/dist/i18n/kit-labels.d.ts +250 -0
- package/dist/i18n/kit-labels.js +184 -0
- package/dist/i18n/kit-labels.js.map +1 -0
- package/dist/index.d.ts +47 -22
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/calc.js +1 -0
- package/dist/lib/calc.js.map +1 -1
- package/dist/lib/cn.js +1 -0
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/dates.js +1 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/logger.js +1 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/safe-storage.js +1 -0
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/search/command-palette.d.ts +11 -1
- package/dist/search/command-palette.js +21 -15
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search.d.ts +2 -0
- package/dist/search.js +3 -0
- package/dist/search.js.map +1 -0
- package/dist/shell/app-shell.d.ts +33 -4
- package/dist/shell/app-shell.js +275 -78
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/option-switcher-menu.js +3 -2
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +6 -0
- package/dist/shell/role-switcher.js +7 -4
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/top-bar.d.ts +15 -7
- package/dist/shell/top-bar.js +3 -5
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/shell/topbar-action-menu.js +3 -2
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.d.ts +5 -2
- package/dist/shell/topbar-controls.js +20 -15
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/shell.d.ts +13 -0
- package/dist/shell.js +8 -0
- package/dist/shell.js.map +1 -0
- package/dist/theme/chart-palette.d.ts +19 -12
- package/dist/theme/chart-palette.js +24 -30
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/color.d.ts +78 -0
- package/dist/theme/color.js +148 -0
- package/dist/theme/color.js.map +1 -0
- package/dist/theme/palette-derive.d.ts +140 -0
- package/dist/theme/palette-derive.js +376 -0
- package/dist/theme/palette-derive.js.map +1 -0
- package/dist/theme/palette-presets.d.ts +27 -5
- package/dist/theme/palette-presets.js +231 -33
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/theme/palette-store.js +1 -0
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/theme-store.js +1 -0
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour.d.ts +2 -1
- package/dist/tour/tour.js +15 -9
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +3 -0
- package/dist/tour.js.map +1 -0
- package/dist/wizard/stepper-nav.js +10 -8
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +18 -2
- package/dist/wizard/types.js +4 -1
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +12 -4
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/validation.js +1 -0
- package/dist/wizard/validation.js.map +1 -1
- package/dist/wizard/wizard-context.js +1 -0
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.js +1 -0
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard/wizard-summary.js +5 -3
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +8 -0
- package/dist/wizard.js +9 -0
- package/dist/wizard.js.map +1 -0
- package/package.json +67 -14
- package/src/chart.ts +22 -0
- package/src/components/account-settings.tsx +275 -0
- package/src/components/alert-banner.tsx +75 -0
- package/src/components/amount-input.tsx +471 -0
- package/src/components/calculator.tsx +252 -0
- package/src/components/chart-zoom.tsx +499 -0
- package/src/components/chart.tsx +363 -0
- package/src/components/checkbox.tsx +222 -0
- package/src/components/chip.tsx +570 -0
- package/src/components/choice-parts.ts +33 -0
- package/src/components/combobox-core.tsx +540 -0
- package/src/components/combobox.tsx +1026 -0
- package/src/components/currency-select.tsx +269 -0
- package/src/components/data-table-filter-popover.tsx +252 -0
- package/src/components/data-table-filters.ts +176 -0
- package/src/components/data-table-labels.ts +190 -0
- package/src/components/data-table-pagination.tsx +126 -0
- package/src/components/data-table-sort.ts +70 -0
- package/src/components/data-table.tsx +1740 -0
- package/src/components/date-picker.tsx +631 -0
- package/src/components/dropdown.tsx +341 -0
- package/src/components/entity-combobox.tsx +215 -0
- package/src/components/facing-pair.ts +86 -0
- package/src/components/field-sync.tsx +472 -0
- package/src/components/file-dropzone.tsx +181 -0
- package/src/components/full-bleed-dialog.tsx +179 -0
- package/src/components/grouped-picker.tsx +157 -0
- package/src/components/hover-menu.tsx +183 -0
- package/src/components/mini-calendar.tsx +554 -0
- package/src/components/modal.tsx +287 -0
- package/src/components/month-picker.tsx +549 -0
- package/src/components/multi-entity-combobox.tsx +226 -0
- package/src/components/multi-select.tsx +303 -0
- package/src/components/number-field.tsx +225 -0
- package/src/components/number-input.tsx +211 -0
- package/src/components/numpad-sheet.tsx +264 -0
- package/src/components/page-contents.tsx +289 -0
- package/src/components/password-strength.tsx +258 -0
- package/src/components/picker-sheet.tsx +251 -0
- package/src/components/popover.tsx +195 -0
- package/src/components/search-field.tsx +146 -0
- package/src/components/series-chart-labels.ts +26 -0
- package/src/components/series-chart.tsx +481 -0
- package/src/components/settings-fields.tsx +68 -0
- package/src/components/signature-pad.tsx +630 -0
- package/src/components/slider.tsx +313 -0
- package/src/components/sparkline.tsx +329 -0
- package/src/components/stat-tile.tsx +448 -0
- package/src/components/swipeable-row.tsx +271 -0
- package/src/components/switch.tsx +170 -0
- package/src/components/time-input.tsx +107 -0
- package/src/components/toggle-group.tsx +153 -0
- package/src/components/toggle-legend.tsx +229 -0
- package/src/components/tooltip.tsx +477 -0
- package/src/components/treemap.tsx +390 -0
- package/src/components/ui.tsx +1284 -0
- package/src/components/use-mobile-reveal.ts +54 -0
- package/src/components/use-table-state.ts +295 -0
- package/src/components/user-avatar.tsx +61 -0
- package/src/components/wizard-stepper.tsx +37 -0
- package/src/data-table.ts +19 -0
- package/src/feedback/feedback-attachment.tsx +246 -0
- package/src/feedback/feedback-dialog.tsx +209 -0
- package/src/feedback/feedback-inbox.tsx +615 -0
- package/src/feedback.ts +8 -0
- package/src/hooks/use-anchored-panel.ts +169 -0
- package/src/hooks/use-anchored-rect.ts +78 -0
- package/src/hooks/use-announce.ts +85 -0
- package/src/hooks/use-body-scroll-lock.ts +94 -0
- package/src/hooks/use-close-transition.ts +99 -0
- package/src/hooks/use-dismiss.ts +77 -0
- package/src/hooks/use-focus-trap.ts +160 -0
- package/src/hooks/use-media-query.ts +23 -0
- package/src/hooks/use-overlay-history.ts +345 -0
- package/src/hooks/use-row-swipe.ts +212 -0
- package/src/i18n/defaults.ts +68 -0
- package/src/i18n/kit-labels.tsx +447 -0
- package/src/index.ts +169 -0
- package/src/lib/calc.ts +309 -0
- package/src/lib/cn.ts +5 -0
- package/src/lib/dates.ts +187 -0
- package/src/lib/logger.ts +73 -0
- package/src/lib/safe-storage.ts +37 -0
- package/src/search/command-palette.tsx +342 -0
- package/src/search.ts +3 -0
- package/src/shell/app-shell.tsx +772 -0
- package/src/shell/option-switcher-menu.tsx +72 -0
- package/src/shell/role-switcher.tsx +46 -0
- package/src/shell/top-bar.tsx +39 -0
- package/src/shell/topbar-action-menu.tsx +91 -0
- package/src/shell/topbar-controls.tsx +229 -0
- package/src/shell.ts +10 -0
- package/src/theme/chart-palette.ts +145 -0
- package/src/theme/color.ts +266 -0
- package/src/theme/palette-derive.ts +681 -0
- package/src/theme/palette-presets.ts +760 -0
- package/src/theme/palette-store.ts +94 -0
- package/src/theme/theme-store.ts +145 -0
- package/src/tour/tour.tsx +586 -0
- package/src/tour.ts +3 -0
- package/src/vite-env.d.ts +26 -0
- package/src/wizard/stepper-nav.tsx +221 -0
- package/src/wizard/types.ts +178 -0
- package/src/wizard/use-wizard.ts +345 -0
- package/src/wizard/validation.ts +37 -0
- package/src/wizard/wizard-context.tsx +29 -0
- package/src/wizard/wizard-step.tsx +14 -0
- package/src/wizard/wizard-summary.tsx +69 -0
- package/src/wizard.ts +14 -0
- package/tokens.css +222 -20
|
@@ -1,15 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useRef, useState } from "react";
|
|
3
4
|
import { createPortal } from "react-dom";
|
|
4
5
|
import { useAnchoredPanel } from "../hooks/use-anchored-panel.js";
|
|
5
6
|
import { useEscapeKey, useOutsideClick } from "../hooks/use-dismiss.js";
|
|
7
|
+
import { useFocusTrap } from "../hooks/use-focus-trap.js";
|
|
8
|
+
import { cn } from "../lib/cn.js";
|
|
9
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
10
|
+
const DEFAULT_POPOVER_LABELS = {
|
|
11
|
+
panel: "Popover"
|
|
12
|
+
};
|
|
6
13
|
const POPOVER_WIDTH = 288;
|
|
7
|
-
function Popover({
|
|
14
|
+
function Popover({
|
|
15
|
+
trigger,
|
|
16
|
+
children,
|
|
17
|
+
width = POPOVER_WIDTH,
|
|
18
|
+
labels,
|
|
19
|
+
panelId,
|
|
20
|
+
className,
|
|
21
|
+
style,
|
|
22
|
+
"aria-label": ariaLabel,
|
|
23
|
+
...rest
|
|
24
|
+
}) {
|
|
8
25
|
const triggerRef = useRef(null);
|
|
9
26
|
const panelRef = useRef(null);
|
|
10
27
|
const [open, setOpen] = useState(false);
|
|
11
28
|
const close = () => setOpen(false);
|
|
12
29
|
const toggle = () => setOpen((v) => !v);
|
|
30
|
+
const text = useKitLabels("popover", DEFAULT_POPOVER_LABELS, labels);
|
|
13
31
|
const placement = useAnchoredPanel(triggerRef, open);
|
|
14
32
|
const rect = placement.rect;
|
|
15
33
|
const pos = rect ? {
|
|
@@ -18,21 +36,52 @@ function Popover({ trigger, children, width = POPOVER_WIDTH }) {
|
|
|
18
36
|
} : null;
|
|
19
37
|
useOutsideClick([triggerRef, panelRef], close, open);
|
|
20
38
|
useEscapeKey(close, open);
|
|
39
|
+
useFocusTrap(panelRef, {
|
|
40
|
+
active: open && pos !== null,
|
|
41
|
+
// The panel keeps a focus one of its own children has already taken. `FilterPopover`
|
|
42
|
+
// autofocuses its text box on mount — that is what the press on "Filter" was FOR,
|
|
43
|
+
// and it is a prop there precisely so the popover keeps it — and a trap that
|
|
44
|
+
// unconditionally focused the container would take it straight back off again,
|
|
45
|
+
// one effect later and invisibly.
|
|
46
|
+
initialFocus: () => {
|
|
47
|
+
const panel = panelRef.current;
|
|
48
|
+
const active = document.activeElement;
|
|
49
|
+
return panel?.contains(active) ? active : panel;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const handleKeyDown = (e) => {
|
|
53
|
+
if (e.key !== "Escape") return;
|
|
54
|
+
e.stopPropagation();
|
|
55
|
+
close();
|
|
56
|
+
};
|
|
21
57
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22
58
|
trigger({ open, toggle, ref: triggerRef }),
|
|
23
59
|
open && pos && createPortal(
|
|
24
60
|
/* @__PURE__ */ jsx(
|
|
25
61
|
"div",
|
|
26
62
|
{
|
|
63
|
+
...rest,
|
|
27
64
|
ref: panelRef,
|
|
65
|
+
role: "dialog",
|
|
66
|
+
id: panelId,
|
|
67
|
+
"aria-label": ariaLabel ?? text.panel,
|
|
68
|
+
tabIndex: -1,
|
|
69
|
+
onKeyDown: handleKeyDown,
|
|
28
70
|
style: {
|
|
71
|
+
// Merged over the caller's, never replaced by it: these four are measured
|
|
72
|
+
// against the trigger every scroll and resize, and a panel that dropped
|
|
73
|
+
// them would paint at the top-left of the viewport.
|
|
74
|
+
...style,
|
|
29
75
|
position: "fixed",
|
|
30
76
|
top: pos.top,
|
|
31
77
|
left: pos.left,
|
|
32
78
|
width,
|
|
33
79
|
maxHeight: placement.maxHeight
|
|
34
80
|
},
|
|
35
|
-
className:
|
|
81
|
+
className: cn(
|
|
82
|
+
"z-50 overflow-y-auto rounded-md border border-[var(--border)] bg-[var(--bg-surface)] p-3 shadow-lg outline-none",
|
|
83
|
+
className
|
|
84
|
+
),
|
|
36
85
|
children: children(close)
|
|
37
86
|
}
|
|
38
87
|
),
|
|
@@ -41,6 +90,7 @@ function Popover({ trigger, children, width = POPOVER_WIDTH }) {
|
|
|
41
90
|
] });
|
|
42
91
|
}
|
|
43
92
|
export {
|
|
93
|
+
DEFAULT_POPOVER_LABELS,
|
|
44
94
|
Popover
|
|
45
95
|
};
|
|
46
96
|
//# sourceMappingURL=popover.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/popover.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport type { ReactNode, RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useAnchoredPanel } from \"../hooks/use-anchored-panel\";\nimport { useEscapeKey, useOutsideClick } from \"../hooks/use-dismiss\";\n\ninterface PopoverProps {\n trigger: (state: { open: boolean; toggle: () => void; ref: RefObject<HTMLButtonElement | null> }) => ReactNode;\n children: (close: () => void) => ReactNode;\n width?: number;\n}\n\nconst POPOVER_WIDTH = 288;\n\n/**\n * Portal-rendered popover anchored under its trigger button. The panel is fixed\n * to the viewport (so table/overflow ancestors can't clip it) and re-aligns on\n * scroll/resize; it closes on outside-click and Escape.\n */\nexport function Popover({ trigger, children, width = POPOVER_WIDTH }: PopoverProps) {\n const triggerRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const [open, setOpen] = useState(false);\n const close = () => setOpen(false);\n const toggle = () => setOpen((v) => !v);\n\n // Align the panel's right edge to the trigger's and clamp it horizontally; the\n // hook owns the vertical half — below by default, flipped above (and height-capped)\n // when that is where the room is, e.g. once a mobile keyboard has eaten the bottom\n // of the screen (feedback #135). It re-measures on scroll/resize.\n const placement = useAnchoredPanel(triggerRef, open);\n const rect = placement.rect;\n const pos = rect\n ? {\n top: placement.top,\n left: Math.min(Math.max(8, rect.right - width), window.innerWidth - width - 8),\n }\n : null;\n\n useOutsideClick([triggerRef, panelRef], close, open);\n useEscapeKey(close, open);\n\n return (\n <>\n {trigger({ open, toggle, ref: triggerRef })}\n {open && pos &&\n createPortal(\n <div\n ref={panelRef}\n style={{\n position: \"fixed\",\n top: pos.top,\n left: pos.left,\n width,\n maxHeight: placement.maxHeight,\n }}\n className=\"z-50 overflow-y-auto rounded-md border border-slate-200 bg-white p-3 shadow-lg dark:border-slate-700 dark:bg-slate-900\"\n >\n {children(close)}\n </div>,\n document.body,\n )}\n </>\n );\n}\n"],"mappings":"AA2CI,mBAIM,KAJN;AA3CJ,SAAS,QAAQ,gBAAgB;AAEjC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,cAAc,uBAAuB;AAQ9C,MAAM,gBAAgB;AAOf,SAAS,QAAQ,EAAE,SAAS,UAAU,QAAQ,cAAc,GAAiB;AAClF,QAAM,aAAa,OAA0B,IAAI;AACjD,QAAM,WAAW,OAAuB,IAAI;AAC5C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,QAAQ,MAAM,QAAQ,KAAK;AACjC,QAAM,SAAS,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;AAMtC,QAAM,YAAY,iBAAiB,YAAY,IAAI;AACnD,QAAM,OAAO,UAAU;AACvB,QAAM,MAAM,OACR;AAAA,IACE,KAAK,UAAU;AAAA,IACf,MAAM,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,QAAQ,KAAK,GAAG,OAAO,aAAa,QAAQ,CAAC;AAAA,EAC/E,IACA;AAEJ,kBAAgB,CAAC,YAAY,QAAQ,GAAG,OAAO,IAAI;AACnD,eAAa,OAAO,IAAI;AAExB,SACE,iCACG;AAAA,YAAQ,EAAE,MAAM,QAAQ,KAAK,WAAW,CAAC;AAAA,IACzC,QAAQ,OACP;AAAA,MACE;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,OAAO;AAAA,YACL,UAAU;AAAA,YACV,KAAK,IAAI;AAAA,YACT,MAAM,IAAI;AAAA,YACV;AAAA,YACA,WAAW,UAAU;AAAA,UACvB;AAAA,UACA,WAAU;AAAA,UAET,mBAAS,KAAK;AAAA;AAAA,MACjB;AAAA,MACA,SAAS;AAAA,IACX;AAAA,KACJ;AAEJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/popover.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, KeyboardEvent, ReactNode, RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useAnchoredPanel } from \"../hooks/use-anchored-panel\";\nimport { useEscapeKey, useOutsideClick } from \"../hooks/use-dismiss\";\nimport { useFocusTrap } from \"../hooks/use-focus-trap\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\n/** The panel's accessible name. A `role=\"dialog\"` with no name announces as \"dialog\"\n * and nothing else, which is worse than the unlabelled div it replaced — so it is a\n * prop with an English default, like every other string this package renders. Callers\n * should pass the control's own name (\"Filter\", \"Choose a date\"); the default is only\n * there so an unmigrated call site is still named. */\nexport interface PopoverLabels {\n panel: string;\n}\n\n// Exported since the kit grew one label tree (`UiKitLabels`, src/i18n): the complete\n// English reference a translator works from has to be able to name every namespace.\nexport const DEFAULT_POPOVER_LABELS: PopoverLabels = {\n panel: \"Popover\",\n};\n\n/**\n * Exported, and extending the `<div>` attributes, so a consumer wrapping this — which is\n * what `DateField` and `FilterPopover` in this very package do — can hand anything\n * through to the PANEL: a `data-tour` anchor for the kit's own guided tour, a test id,\n * an `aria-describedby`, or an `aria-label` naming this particular popover.\n *\n * `children` is omitted from those attributes rather than inherited: here it is a render\n * prop taking the panel's `close`, not a `ReactNode`, so the two cannot be reconciled.\n */\nexport interface PopoverProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n trigger: (state: { open: boolean; toggle: () => void; ref: RefObject<HTMLButtonElement | null> }) => ReactNode;\n children: (close: () => void) => ReactNode;\n width?: number;\n labels?: Partial<PopoverLabels>;\n /** Id for the panel, so a trigger wearing `role=\"combobox\"` can `aria-controls` it.\n * Required by ARIA on that role, and the panel is rendered here rather than by the\n * caller, so the id has to come in from outside. */\n panelId?: string;\n /** Extra classes for the PANEL. The trigger is the caller's own element. */\n className?: string;\n}\n\nconst POPOVER_WIDTH = 288;\n\n/**\n * Portal-rendered popover anchored under its trigger button. The panel is fixed\n * to the viewport (so table/overflow ancestors can't clip it) and re-aligns on\n * scroll/resize; it closes on outside-click and Escape.\n *\n * ## Trapped, not merely focused — and why, because it is the closer call\n *\n * The audit's finding was that this panel *\"portals out of the tab order with no role\n * and no focus move\"*: Tab from the trigger went straight past the panel into the page\n * behind it, so the calendar, the calculator keypad and every column filter in the kit\n * were mouse-only. Two shapes fix that, and a popover is not automatically a dialog, so\n * this was chosen rather than inherited:\n *\n * - **The light one** — move focus in on open, hand it back on close, and let Tab walk\n * OUT of the panel into whatever follows the trigger. That is the right shape for a\n * non-modal popover sitting inline in the document.\n * - **The trap** — Tab cycles inside the panel; Escape and a click outside are the\n * ways out. What is here.\n *\n * The deciding fact is the portal. This panel is rendered to the END of `<body>`, not\n * beside its trigger, so \"let Tab walk out\" does not mean \"carry on from the trigger\" —\n * it means land at the end of the document, several hundred controls from where the\n * user was. Tabbing out of it would be a worse place to arrive than the one the finding\n * complains about, and fixing that properly needs a sentinel-and-return dance that\n * nothing in this package has. The trap gets the user back to the trigger every time,\n * because `useFocusTrap` restores focus on close.\n *\n * And the panels this kit actually opens are self-contained: pick a date, tap out a\n * sum, narrow a column. Each of them commits and closes, and each already has two\n * documented ways out wired above — Escape and an outside press — so containment costs\n * a keyboard user nothing they had.\n *\n * **It stays non-modal in every other respect**, deliberately: no backdrop, no body\n * scroll lock, and no `aria-modal`, so the page behind is still readable by a screen\n * reader, still scrollable, and still clickable — clicking it is what dismisses this.\n * Only Tab is contained. A popover that took the page away as well would be a\n * {@link Modal}, and there is one of those.\n */\nexport function Popover({\n trigger,\n children,\n width = POPOVER_WIDTH,\n labels,\n panelId,\n className,\n style,\n \"aria-label\": ariaLabel,\n ...rest\n}: PopoverProps) {\n const triggerRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const [open, setOpen] = useState(false);\n const close = () => setOpen(false);\n const toggle = () => setOpen((v) => !v);\n // prop > `<UiKitProvider labels={{ popover }}>` > English. The provider's\n // `popover.panel` is only the last-resort name for a panel no caller named.\n const text = useKitLabels(\"popover\", DEFAULT_POPOVER_LABELS, labels);\n\n // Align the panel's right edge to the trigger's and clamp it horizontally; the\n // hook owns the vertical half — below by default, flipped above (and height-capped)\n // when that is where the room is, e.g. once a mobile keyboard has eaten the bottom\n // of the screen (feedback #135). It re-measures on scroll/resize.\n const placement = useAnchoredPanel(triggerRef, open);\n const rect = placement.rect;\n const pos = rect\n ? {\n top: placement.top,\n left: Math.min(Math.max(8, rect.right - width), window.innerWidth - width - 8),\n }\n : null;\n\n useOutsideClick([triggerRef, panelRef], close, open);\n useEscapeKey(close, open);\n\n useFocusTrap(panelRef, {\n active: open && pos !== null,\n // The panel keeps a focus one of its own children has already taken. `FilterPopover`\n // autofocuses its text box on mount — that is what the press on \"Filter\" was FOR,\n // and it is a prop there precisely so the popover keeps it — and a trap that\n // unconditionally focused the container would take it straight back off again,\n // one effect later and invisibly.\n initialFocus: () => {\n const panel = panelRef.current;\n const active = document.activeElement;\n return panel?.contains(active) ? (active as HTMLElement) : panel;\n },\n });\n\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key !== \"Escape\") return;\n // `useEscapeKey` above listens on the document and still covers the case where\n // focus somehow is not in here. This handler is for the case that focus IS, which\n // is now the normal one — and it exists to STOP the key, because a popover opened\n // from inside a `Modal` (the calculator on an amount field is exactly that) would\n // otherwise have its Escape travel on up the React tree and close the dialog\n // underneath as well. Closing twice is harmless — `close` is a state setter, not a\n // caller's callback — so the two paths cannot double-fire anything.\n e.stopPropagation();\n close();\n };\n\n return (\n <>\n {trigger({ open, toggle, ref: triggerRef })}\n {open && pos &&\n createPortal(\n <div\n // `...rest` first: the role, the id the trigger's `aria-controls` points at,\n // the `tabIndex` the trap needs and the measured position are what make this\n // a popover, and a caller reaching for a `data-tour` anchor must not be able\n // to remove one of them by accident.\n {...rest}\n ref={panelRef}\n role=\"dialog\"\n id={panelId}\n // The caller's own `aria-label` wins over {@link PopoverLabels.panel}, which\n // is the package's English fallback for an unnamed panel. Both name the same\n // element; the DOM spelling is the one being standardised on, so where both\n // are present it is the one that survives.\n aria-label={ariaLabel ?? text.panel}\n // Without it the panel cannot take focus at all, and the trap above would\n // silently do nothing on a panel whose children are not yet tabbable.\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n style={{\n // Merged over the caller's, never replaced by it: these four are measured\n // against the trigger every scroll and resize, and a panel that dropped\n // them would paint at the top-left of the viewport.\n ...style,\n position: \"fixed\",\n top: pos.top,\n left: pos.left,\n width,\n maxHeight: placement.maxHeight,\n }}\n className={cn(\n \"z-50 overflow-y-auto rounded-md border border-[var(--border)] bg-[var(--bg-surface)] p-3 shadow-lg outline-none\",\n className,\n )}\n >\n {children(close)}\n </div>,\n document.body,\n )}\n </>\n );\n}\n"],"mappings":";AAsJI,mBAIM,KAJN;AAtJJ,SAAS,QAAQ,gBAAgB;AAEjC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,cAAc,uBAAuB;AAC9C,SAAS,oBAAoB;AAC7B,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAatB,MAAM,yBAAwC;AAAA,EACnD,OAAO;AACT;AAwBA,MAAM,gBAAgB;AAwCf,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,GAAG;AACL,GAAiB;AACf,QAAM,aAAa,OAA0B,IAAI;AACjD,QAAM,WAAW,OAAuB,IAAI;AAC5C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,QAAQ,MAAM,QAAQ,KAAK;AACjC,QAAM,SAAS,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;AAGtC,QAAM,OAAO,aAAa,WAAW,wBAAwB,MAAM;AAMnE,QAAM,YAAY,iBAAiB,YAAY,IAAI;AACnD,QAAM,OAAO,UAAU;AACvB,QAAM,MAAM,OACR;AAAA,IACE,KAAK,UAAU;AAAA,IACf,MAAM,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,QAAQ,KAAK,GAAG,OAAO,aAAa,QAAQ,CAAC;AAAA,EAC/E,IACA;AAEJ,kBAAgB,CAAC,YAAY,QAAQ,GAAG,OAAO,IAAI;AACnD,eAAa,OAAO,IAAI;AAExB,eAAa,UAAU;AAAA,IACrB,QAAQ,QAAQ,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMxB,cAAc,MAAM;AAClB,YAAM,QAAQ,SAAS;AACvB,YAAM,SAAS,SAAS;AACxB,aAAO,OAAO,SAAS,MAAM,IAAK,SAAyB;AAAA,IAC7D;AAAA,EACF,CAAC;AAED,QAAM,gBAAgB,CAAC,MAAqC;AAC1D,QAAI,EAAE,QAAQ,SAAU;AAQxB,MAAE,gBAAgB;AAClB,UAAM;AAAA,EACR;AAEA,SACE,iCACG;AAAA,YAAQ,EAAE,MAAM,QAAQ,KAAK,WAAW,CAAC;AAAA,IACzC,QAAQ,OACP;AAAA,MACE;AAAA,QAAC;AAAA;AAAA,UAKE,GAAG;AAAA,UACJ,KAAK;AAAA,UACL,MAAK;AAAA,UACL,IAAI;AAAA,UAKJ,cAAY,aAAa,KAAK;AAAA,UAG9B,UAAU;AAAA,UACV,WAAW;AAAA,UACX,OAAO;AAAA;AAAA;AAAA;AAAA,YAIL,GAAG;AAAA,YACH,UAAU;AAAA,YACV,KAAK,IAAI;AAAA,YACT,MAAM,IAAI;AAAA,YACV;AAAA,YACA,WAAW,UAAU;AAAA,UACvB;AAAA,UACA,WAAW;AAAA,YACT;AAAA,YACA;AAAA,UACF;AAAA,UAEC,mBAAS,KAAK;AAAA;AAAA,MACjB;AAAA,MACA,SAAS;AAAA,IACX;AAAA,KACJ;AAEJ;","names":[]}
|
|
@@ -1,23 +1,41 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { InputHTMLAttributes } from 'react';
|
|
3
3
|
|
|
4
|
-
interface
|
|
4
|
+
interface SearchFieldOwnProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "type" | "aria-label"> {
|
|
5
5
|
value: string;
|
|
6
6
|
/** The typed text. Emitted as a plain string, not an event: every caller of the
|
|
7
7
|
* four this replaced wanted the string, and one of them held it in a URL param
|
|
8
8
|
* rather than in state. */
|
|
9
9
|
onChange: (value: string) => void;
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The field's accessible name.
|
|
12
|
+
*
|
|
13
|
+
* @deprecated Pass `aria-label` instead — the DOM spelling this kit standardised on
|
|
14
|
+
* when it found three names for one idea (`ariaLabel`, `aria-label`, and this).
|
|
15
|
+
* Unchanged and still supported; it names the field only when `aria-label` is
|
|
16
|
+
* absent, so a consumer can migrate one call site at a time.
|
|
17
|
+
*/
|
|
18
|
+
label?: string;
|
|
15
19
|
/** Names the clear button. Omit it and no clear button is rendered — which is a
|
|
16
20
|
* decision, not a default: a filter you can type into and not untype is the
|
|
17
21
|
* complaint that produced the button on three of these four screens. */
|
|
18
22
|
clearLabel?: string;
|
|
19
|
-
className?: string;
|
|
20
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* One of the two spellings is REQUIRED, and the union is how that survives the
|
|
26
|
+
* deprecation. Making `label` merely optional would have been the easy change and the
|
|
27
|
+
* wrong one: the whole reason this component exists is that two of the four boxes it
|
|
28
|
+
* replaced were named by nothing but their placeholder — which disappears the moment
|
|
29
|
+
* someone types, i.e. exactly when a screen-reader user is working in the field. A
|
|
30
|
+
* caller now has a choice of spelling; it still has no option to pass neither.
|
|
31
|
+
*/
|
|
32
|
+
type SearchFieldProps = SearchFieldOwnProps & ({
|
|
33
|
+
"aria-label": string;
|
|
34
|
+
label?: string;
|
|
35
|
+
} | {
|
|
36
|
+
label: string;
|
|
37
|
+
"aria-label"?: string;
|
|
38
|
+
});
|
|
21
39
|
/**
|
|
22
40
|
* The page-level filter box: a search icon, a `type="search"` input, and a clear
|
|
23
41
|
* "×" that appears once there is something to clear.
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { forwardRef, useRef } from "react";
|
|
3
4
|
import { Search, X } from "lucide-react";
|
|
4
5
|
import { cn } from "../lib/cn.js";
|
|
5
6
|
import { FIELD_BASE } from "./ui.js";
|
|
6
|
-
const SearchField = forwardRef(function SearchField2({ value, onChange, label, clearLabel, className, placeholder, ...rest }, ref) {
|
|
7
|
+
const SearchField = forwardRef(function SearchField2({ value, onChange, label, clearLabel, className, placeholder, "aria-label": ariaLabel, ...rest }, ref) {
|
|
7
8
|
const innerRef = useRef(null);
|
|
9
|
+
const name = ariaLabel ?? label;
|
|
8
10
|
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
9
11
|
/* @__PURE__ */ jsx(
|
|
10
12
|
Search,
|
|
11
13
|
{
|
|
12
14
|
"aria-hidden": true,
|
|
13
|
-
className: "pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-
|
|
15
|
+
className: "pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]"
|
|
14
16
|
}
|
|
15
17
|
),
|
|
16
18
|
/* @__PURE__ */ jsx(
|
|
@@ -24,8 +26,8 @@ const SearchField = forwardRef(function SearchField2({ value, onChange, label, c
|
|
|
24
26
|
type: "search",
|
|
25
27
|
value,
|
|
26
28
|
onChange: (e) => onChange(e.target.value),
|
|
27
|
-
"aria-label":
|
|
28
|
-
placeholder: placeholder ??
|
|
29
|
+
"aria-label": name,
|
|
30
|
+
placeholder: placeholder ?? name,
|
|
29
31
|
name: "search",
|
|
30
32
|
autoComplete: "off",
|
|
31
33
|
autoCapitalize: "none",
|
|
@@ -51,7 +53,7 @@ const SearchField = forwardRef(function SearchField2({ value, onChange, label, c
|
|
|
51
53
|
innerRef.current?.focus();
|
|
52
54
|
},
|
|
53
55
|
"aria-label": clearLabel,
|
|
54
|
-
className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-
|
|
56
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-[var(--text-placeholder)] hover:text-[var(--text-secondary)]",
|
|
55
57
|
children: /* @__PURE__ */ jsx(X, { className: "size-4" })
|
|
56
58
|
}
|
|
57
59
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/search-field.tsx"],"sourcesContent":["import { forwardRef, useRef } from \"react\";\nimport type { InputHTMLAttributes } from \"react\";\nimport { Search, X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { FIELD_BASE } from \"./ui\";\n\
|
|
1
|
+
{"version":3,"sources":["../../src/components/search-field.tsx"],"sourcesContent":["import { forwardRef, useRef } from \"react\";\nimport type { InputHTMLAttributes } from \"react\";\nimport { Search, X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { FIELD_BASE } from \"./ui\";\n\ninterface SearchFieldOwnProps\n extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"value\" | \"onChange\" | \"type\" | \"aria-label\"\n > {\n value: string;\n /** The typed text. Emitted as a plain string, not an event: every caller of the\n * four this replaced wanted the string, and one of them held it in a URL param\n * rather than in state. */\n onChange: (value: string) => void;\n /**\n * The field's accessible name.\n *\n * @deprecated Pass `aria-label` instead — the DOM spelling this kit standardised on\n * when it found three names for one idea (`ariaLabel`, `aria-label`, and this).\n * Unchanged and still supported; it names the field only when `aria-label` is\n * absent, so a consumer can migrate one call site at a time.\n */\n label?: string;\n /** Names the clear button. Omit it and no clear button is rendered — which is a\n * decision, not a default: a filter you can type into and not untype is the\n * complaint that produced the button on three of these four screens. */\n clearLabel?: string;\n}\n\n/**\n * One of the two spellings is REQUIRED, and the union is how that survives the\n * deprecation. Making `label` merely optional would have been the easy change and the\n * wrong one: the whole reason this component exists is that two of the four boxes it\n * replaced were named by nothing but their placeholder — which disappears the moment\n * someone types, i.e. exactly when a screen-reader user is working in the field. A\n * caller now has a choice of spelling; it still has no option to pass neither.\n */\nexport type SearchFieldProps = SearchFieldOwnProps &\n ({ \"aria-label\": string; label?: string } | { label: string; \"aria-label\"?: string });\n\n/**\n * The page-level filter box: a search icon, a `type=\"search\"` input, and a clear\n * \"×\" that appears once there is something to clear.\n *\n * ## Why it is a component\n *\n * Keksdose had four of these and no two agreed. Two were character-identical\n * twenty-line copies (the settings catalogue filter and the tour list filter) —\n * the same absolute icon at `left-3`, the same `pl-9 pr-9`, the same\n * `[&::-webkit-search-cancel-button]:appearance-none`, the same conditional clear\n * button at `right-2` — differing only in which state setter they called. The\n * third (the admin support queue) was the same idea at `left-2`/`size-3.5`/`pl-7`\n * with no clear button and no `type=\"search\"`, so it had neither the `searchbox`\n * role nor a way to empty itself. The fourth (the bank picker) was a bare `<Input\n * placeholder=\"Search\">`: no icon, no clear, and no accessible name at all once\n * the placeholder went away.\n *\n * None of that is a design decision anyone made; it is four people solving the\n * same problem on four days. A shared component is how the fourth screen gets the\n * clear button the first one earned.\n *\n * ## The two X's\n *\n * `type=\"search\"` is what gives the field its `searchbox` role — and, in Chrome, a\n * NATIVE clear cross that paints right next to ours, so a non-empty box shows two\n * X's an inch apart. Ours is the one that survives: it carries an accessible name\n * and matches the app's other icon buttons, and it returns focus to the input so\n * the next query can be typed straight away.\n */\nexport const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(function SearchField(\n { value, onChange, label, clearLabel, className, placeholder, \"aria-label\": ariaLabel, ...rest },\n ref,\n) {\n const innerRef = useRef<HTMLInputElement>(null);\n // Whichever spelling the caller used. It is the accessible name AND the placeholder\n // fallback, so a box named only by `aria-label` still says what it is before anyone\n // has typed in it — the same deal `label` has always had.\n const name = ariaLabel ?? label;\n return (\n <div className={cn(\"relative\", className)}>\n <Search\n aria-hidden\n className=\"pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]\"\n />\n <input\n ref={(node) => {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n }}\n type=\"search\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n aria-label={name}\n placeholder={placeholder ?? name}\n // Keksdose live #333: *\"Entering the search here — is that set as password?\n // … I got prompted for remembering my name or mail address\"*, typed into the\n // settings filter on a phone.\n //\n // A `type=\"search\"` input with no `name` and no `autocomplete` is a field\n // Chrome's autofill classifies by GUESSWORK, from the labels around it and\n // the other fields on the page — and a settings page has an email and a\n // password on it. Naming the field and declaring the intent is what takes it\n // out of that guess: `autocomplete=\"off\"` says it is not part of any profile,\n // and a `name` of \"search\" is the strongest hint the heuristics take.\n //\n // The three that follow are not about autofill but about the same keyboard:\n // a filter box that capitalises the first letter and autocorrects \"2fa\" to\n // \"2FA\" is fighting the person typing into it. Declared BEFORE `{...rest}` so\n // a caller with a reason can still override any of them.\n name=\"search\"\n autoComplete=\"off\"\n autoCapitalize=\"none\"\n autoCorrect=\"off\"\n spellCheck={false}\n {...rest}\n className={cn(\n FIELD_BASE,\n \"pl-9\",\n // Room for our own \"×\" only when there is one; a field that can't be\n // cleared has no reason to reserve the space.\n clearLabel === undefined ? \"pr-3\" : \"pr-9\",\n \"[&::-webkit-search-cancel-button]:appearance-none\",\n )}\n />\n {clearLabel !== undefined && value !== \"\" && (\n <button\n type=\"button\"\n onClick={() => {\n onChange(\"\");\n // Clearing is the start of the next query far more often than it is the\n // end of this one, so the caret stays where it can be typed into.\n innerRef.current?.focus();\n }}\n aria-label={clearLabel}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-[var(--text-placeholder)] hover:text-[var(--text-secondary)]\"\n >\n <X className=\"size-4\" />\n </button>\n )}\n </div>\n );\n});\nSearchField.displayName = \"SearchField\";\n"],"mappings":";AAiFI,SACE,KADF;AAjFJ,SAAS,YAAY,cAAc;AAEnC,SAAS,QAAQ,SAAS;AAC1B,SAAS,UAAU;AACnB,SAAS,kBAAkB;AAmEpB,MAAM,cAAc,WAA+C,SAASA,aACjF,EAAE,OAAO,UAAU,OAAO,YAAY,WAAW,aAAa,cAAc,WAAW,GAAG,KAAK,GAC/F,KACA;AACA,QAAM,WAAW,OAAyB,IAAI;AAI9C,QAAM,OAAO,aAAa;AAC1B,SACE,qBAAC,SAAI,WAAW,GAAG,YAAY,SAAS,GACtC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAU;AAAA;AAAA,IACZ;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,CAAC,SAAS;AACb,mBAAS,UAAU;AACnB,cAAI,OAAO,QAAQ,WAAY,KAAI,IAAI;AAAA,mBAC9B,IAAK,KAAI,UAAU;AAAA,QAC9B;AAAA,QACA,MAAK;AAAA,QACL;AAAA,QACA,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,QACxC,cAAY;AAAA,QACZ,aAAa,eAAe;AAAA,QAgB5B,MAAK;AAAA,QACL,cAAa;AAAA,QACb,gBAAe;AAAA,QACf,aAAY;AAAA,QACZ,YAAY;AAAA,QACX,GAAG;AAAA,QACJ,WAAW;AAAA,UACT;AAAA,UACA;AAAA;AAAA;AAAA,UAGA,eAAe,SAAY,SAAS;AAAA,UACpC;AAAA,QACF;AAAA;AAAA,IACF;AAAA,IACC,eAAe,UAAa,UAAU,MACrC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM;AACb,mBAAS,EAAE;AAGX,mBAAS,SAAS,MAAM;AAAA,QAC1B;AAAA,QACA,cAAY;AAAA,QACZ,WAAU;AAAA,QAEV,8BAAC,KAAE,WAAU,UAAS;AAAA;AAAA,IACxB;AAAA,KAEJ;AAEJ,CAAC;AACD,YAAY,cAAc;","names":["SearchField"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface SeriesChartLabels {
|
|
2
|
+
/** The button that puts a zoomed chart back to its full extent. Visible text. */
|
|
3
|
+
resetZoom: string;
|
|
4
|
+
/** Accessible name of the drag surface laid over the plot. It is the only place the
|
|
5
|
+
* gesture is explained, so it says how the drag is read and how to undo it. */
|
|
6
|
+
zoomHint: string;
|
|
7
|
+
/** What a chart with nothing to draw says, when the caller passes no `empty`. */
|
|
8
|
+
empty: string;
|
|
9
|
+
/** Accessible name of a `ToggleLegend` — a group of switches, one per series. */
|
|
10
|
+
legend: string;
|
|
11
|
+
}
|
|
12
|
+
declare const DEFAULT_SERIES_CHART_LABELS: SeriesChartLabels;
|
|
13
|
+
|
|
14
|
+
export { DEFAULT_SERIES_CHART_LABELS, type SeriesChartLabels };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
const DEFAULT_SERIES_CHART_LABELS = {
|
|
3
|
+
resetZoom: "Reset zoom",
|
|
4
|
+
zoomHint: "Drag to zoom: a roughly square selection zooms both axes, a long thin one only its own. Double-click to reset.",
|
|
5
|
+
empty: "No data",
|
|
6
|
+
legend: "Series"
|
|
7
|
+
};
|
|
8
|
+
export {
|
|
9
|
+
DEFAULT_SERIES_CHART_LABELS
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=series-chart-labels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/series-chart-labels.ts"],"sourcesContent":["// All user-facing strings for the series chart family — `SeriesChart`, its drag-to-zoom\n// layer and `ToggleLegend` — as the `seriesChart` namespace of `<UiKitProvider labels>`,\n// and per chart the `labels` prop over that.\n//\n// A module of its own, with no recharts in it, so the label registry (src/i18n) can\n// import the defaults without pulling the chart library in behind them.\n\nexport interface SeriesChartLabels {\n /** The button that puts a zoomed chart back to its full extent. Visible text. */\n resetZoom: string;\n /** Accessible name of the drag surface laid over the plot. It is the only place the\n * gesture is explained, so it says how the drag is read and how to undo it. */\n zoomHint: string;\n /** What a chart with nothing to draw says, when the caller passes no `empty`. */\n empty: string;\n /** Accessible name of a `ToggleLegend` — a group of switches, one per series. */\n legend: string;\n}\n\nexport const DEFAULT_SERIES_CHART_LABELS: SeriesChartLabels = {\n resetZoom: \"Reset zoom\",\n zoomHint:\n \"Drag to zoom: a roughly square selection zooms both axes, a long thin one only its own. Double-click to reset.\",\n empty: \"No data\",\n legend: \"Series\",\n};\n"],"mappings":";AAmBO,MAAM,8BAAiD;AAAA,EAC5D,WAAW;AAAA,EACX,UACE;AAAA,EACF,OAAO;AAAA,EACP,QAAQ;AACV;","names":[]}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { ZoomBinding } from './chart-zoom.js';
|
|
4
|
+
import { SeriesChartLabels } from './series-chart-labels.js';
|
|
5
|
+
import 'recharts';
|
|
6
|
+
|
|
7
|
+
interface SeriesChartSeries {
|
|
8
|
+
/** The row key this line reads — and the `--color-<key>` it is painted with, so it
|
|
9
|
+
* must be a CSS identifier. Build keys out of data with {@link seriesKey}. */
|
|
10
|
+
key: string;
|
|
11
|
+
label: ReactNode;
|
|
12
|
+
/** Any CSS colour the chart shell accepts (hex, rgb/hsl, `var(--token)`). Default:
|
|
13
|
+
* the kit's categorical ramp by position, `paletteFor(index)`. */
|
|
14
|
+
color?: string;
|
|
15
|
+
/** Marks the PAIRED line of a pair — the return stroke against the out-stroke, the
|
|
16
|
+
* reference against the measurement. Shorthand for `dash: 1`. */
|
|
17
|
+
dashed?: boolean;
|
|
18
|
+
/** Which of {@link STROKE_PATTERNS} this line takes, for charts where the stroke says
|
|
19
|
+
* WHICH QUANTITY and the colour says WHICH MEASUREMENT. */
|
|
20
|
+
dash?: number;
|
|
21
|
+
/** `stepAfter`, for a whole-number channel that jumps rather than travels — a
|
|
22
|
+
* straight line between index 0 and index 1 draws an index of 0.5, which does not
|
|
23
|
+
* exist. Drawn in the {@link STEP_DASH} pattern, whatever `dash` says. */
|
|
24
|
+
step?: boolean;
|
|
25
|
+
/** Which of `axes` this line is measured on. Default: `"y"`, the single one. */
|
|
26
|
+
axis?: string;
|
|
27
|
+
}
|
|
28
|
+
interface SeriesChartAxisShape {
|
|
29
|
+
id: string;
|
|
30
|
+
/**
|
|
31
|
+
* Which side of the plot the ticks stand on. Left unless said otherwise — and said
|
|
32
|
+
* otherwise for the right-hand chart of a FACING pair (see `facingAxes`), whose
|
|
33
|
+
* scales go on the outside so neither column of numbers stands between two pictures
|
|
34
|
+
* being compared.
|
|
35
|
+
*/
|
|
36
|
+
orientation?: "left" | "right";
|
|
37
|
+
/** Tints the ticks — and the title too, when exactly one series is measured on this
|
|
38
|
+
* axis (see {@link soleSeriesColor}). */
|
|
39
|
+
color?: string;
|
|
40
|
+
/** What the TICKS need, in px. The title's strip is added on top. Default 48. */
|
|
41
|
+
width?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Pin the scale instead of fitting it to this chart's data. For a ROW of charts
|
|
44
|
+
* read against each other: auto-fitted, a 200 N loop and a 2000 N one draw the
|
|
45
|
+
* identical picture, and the comparison the layout promises is the one it fails.
|
|
46
|
+
*/
|
|
47
|
+
domain?: [number, number];
|
|
48
|
+
/** A tick, formatted — as a BARE number. The unit is said once, in the title; the
|
|
49
|
+
* tooltip gets it from `valueFormat`. Default: `Intl.NumberFormat` in the kit's
|
|
50
|
+
* locale, up to two fraction digits. */
|
|
51
|
+
format?: (value: number) => string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A y axis — and a VISIBLE one says what it measures.
|
|
55
|
+
*
|
|
56
|
+
* A union rather than an optional field, so the rule is the type's: a column of numbers
|
|
57
|
+
* whose quantity and unit are not written down is a column somebody reads as whatever
|
|
58
|
+
* they expected. `title: ""` is the explicit opt-out (the right-hand chart of a facing
|
|
59
|
+
* pair, whose neighbour already said it). A HIDDEN axis scales its lines and draws
|
|
60
|
+
* nothing, so there is nowhere for a title to go.
|
|
61
|
+
*/
|
|
62
|
+
type SeriesChartAxis = SeriesChartAxisShape & ({
|
|
63
|
+
title: string;
|
|
64
|
+
hide?: false;
|
|
65
|
+
} | {
|
|
66
|
+
title?: string;
|
|
67
|
+
hide: true;
|
|
68
|
+
});
|
|
69
|
+
interface SeriesChartX {
|
|
70
|
+
/** Row key of the abscissa. Default `"x"`, which is what {@link mergeSeries} writes. */
|
|
71
|
+
key?: string;
|
|
72
|
+
/** What the abscissa is, with its unit. Drawn under the axis. */
|
|
73
|
+
title?: string;
|
|
74
|
+
/** A tick, formatted — bare, like the y axes'. */
|
|
75
|
+
format?: (value: number) => string;
|
|
76
|
+
/**
|
|
77
|
+
* Whether this chart prints the abscissa's ticks. On by default; off for every chart
|
|
78
|
+
* of a stack sharing one x window but the bottom one, which prints them for all. It
|
|
79
|
+
* costs no alignment: the x mapping is decided by the margins and the y bands.
|
|
80
|
+
*/
|
|
81
|
+
ticks?: boolean;
|
|
82
|
+
/** The tooltip's heading. Default: the abscissa value through `format`. */
|
|
83
|
+
label?: (value: number) => ReactNode;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A vertical segment in data coordinates — two y at one x, which a row-per-abscissa
|
|
87
|
+
* chart structurally cannot draw. For CLOSING a shape the series already drew: the
|
|
88
|
+
* turnarounds of a hysteresis loop, where the abscissa stands still while the value
|
|
89
|
+
* falls from one branch to the other.
|
|
90
|
+
*/
|
|
91
|
+
interface SeriesChartSpan {
|
|
92
|
+
/** Distinct within the chart. */
|
|
93
|
+
key: string;
|
|
94
|
+
/** Which y axis `from`/`to` are measured on. Default `"y"`. */
|
|
95
|
+
axis?: string;
|
|
96
|
+
x: number;
|
|
97
|
+
from: number;
|
|
98
|
+
to: number;
|
|
99
|
+
/** Default: `var(--text-muted)`. */
|
|
100
|
+
color?: string;
|
|
101
|
+
}
|
|
102
|
+
interface SeriesChartProps {
|
|
103
|
+
/** One row per abscissa value. A missing key is a hole (see `connectNulls`). */
|
|
104
|
+
rows: Record<string, number>[];
|
|
105
|
+
series: SeriesChartSeries[];
|
|
106
|
+
/** Default: one untitled axis, `"y"`. */
|
|
107
|
+
axes?: SeriesChartAxis[];
|
|
108
|
+
x?: SeriesChartX;
|
|
109
|
+
/** A value in the tooltip, where a number stands on its own and needs its unit.
|
|
110
|
+
* Default: `Intl.NumberFormat` in the kit's locale. */
|
|
111
|
+
valueFormat?: (value: number) => string;
|
|
112
|
+
/** Tailwind height class. Default `h-72`. The empty state takes it too, so a chart
|
|
113
|
+
* losing its last line does not relayout the page under it. */
|
|
114
|
+
height?: string;
|
|
115
|
+
/** Bridge holes left by sources sampled on different grids. See {@link mergeSeries}. */
|
|
116
|
+
connectNulls?: boolean;
|
|
117
|
+
/** Shown, centred at the chart's height, instead of an empty chart. Default: the
|
|
118
|
+
* `seriesChart.empty` label. `null` shows nothing. */
|
|
119
|
+
empty?: ReactNode;
|
|
120
|
+
/**
|
|
121
|
+
* Milliseconds for a line to draw itself in. Off by default: a chart beside a live
|
|
122
|
+
* editor re-renders on every keystroke, and an animation restarted mid-flight reads
|
|
123
|
+
* as the curve twitching. Turn it on where series come and go by choice.
|
|
124
|
+
*/
|
|
125
|
+
animationMs?: number;
|
|
126
|
+
spans?: SeriesChartSpan[];
|
|
127
|
+
/** Supplied by `withChartZoom` and by nothing else. */
|
|
128
|
+
zoom?: ZoomBinding;
|
|
129
|
+
/** Per-chart strings over `<UiKitProvider labels={{ seriesChart }}>`. */
|
|
130
|
+
labels?: Partial<SeriesChartLabels>;
|
|
131
|
+
/** BCP 47 tag for the default number formats. Default: the provider's. */
|
|
132
|
+
locale?: string;
|
|
133
|
+
/** Onto the chart's root (`ChartContainer`) — e.g. an app's own grid ink. */
|
|
134
|
+
className?: string;
|
|
135
|
+
}
|
|
136
|
+
/** How much of an axis band the rotated title takes, in px. Reserved rather than
|
|
137
|
+
* shared with the ticks: a title merely offset inside the band prints on top of a
|
|
138
|
+
* five-figure tick, and which gives way depends on the data. */
|
|
139
|
+
declare const AXIS_TITLE_STRIP = 16;
|
|
140
|
+
/** The width the ticks of a y axis get when a caller does not say. */
|
|
141
|
+
declare const AXIS_TICK_WIDTH = 48;
|
|
142
|
+
/** The band a fitted axis draws, padded — and a real band for constant data, which is
|
|
143
|
+
* a zero-height domain recharts cannot scale into. `undefined` when nothing finite. */
|
|
144
|
+
declare function paddedDomain(values: Iterable<number>): [number, number] | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* {@link paddedDomain} for a caller that has already walked its own data — so a row
|
|
147
|
+
* of charts can share one scale without building an array to find two numbers in, and
|
|
148
|
+
* without a second padding rule. `low > high` means "nothing was seen": `undefined`.
|
|
149
|
+
*/
|
|
150
|
+
declare function padBand(low: number, high: number): [number, number] | undefined;
|
|
151
|
+
/** The whole band a y axis occupies: its ticks, and its title when it draws one. For
|
|
152
|
+
* lining a heading up over a PLOT rather than over its column. */
|
|
153
|
+
declare function axisBandWidth(tickWidth: number | undefined, titled: boolean): number;
|
|
154
|
+
/**
|
|
155
|
+
* The colour an axis TITLE takes, or `undefined` for the neutral one.
|
|
156
|
+
*
|
|
157
|
+
* An axis measuring exactly one line is that line's, and saying so in the title lets a
|
|
158
|
+
* reader find a channel's scale without counting axes. An axis measuring several is
|
|
159
|
+
* nobody's — a title in the first one's colour would be claiming it. Counted off the
|
|
160
|
+
* series, so toggling a second line onto an axis gives the colour up in the same render.
|
|
161
|
+
*/
|
|
162
|
+
declare function soleSeriesColor(axis: Pick<SeriesChartAxisShape, "id" | "color">, series: readonly Pick<SeriesChartSeries, "axis">[]): string | undefined;
|
|
163
|
+
/** The single-axis case, which is most charts. `width` is what the TICKS need;
|
|
164
|
+
* `orientation` is which side they stand on. */
|
|
165
|
+
declare function oneAxis(format: ((value: number) => string) | undefined, title: string, width?: number, orientation?: "left" | "right"): SeriesChartAxis[];
|
|
166
|
+
/** One source's samples: an abscissa and the channels measured against it. */
|
|
167
|
+
interface SeriesSource {
|
|
168
|
+
x: number[];
|
|
169
|
+
channels: Record<string, number[]>;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Merge sources sampled on different grids into rows the chart can draw.
|
|
173
|
+
*
|
|
174
|
+
* Merged ON THE ABSCISSA VALUE ITSELF, never interpolated onto a common grid: a line
|
|
175
|
+
* through invented points looks exactly like one through measured ones. Where a source
|
|
176
|
+
* has no sample the key is absent, and `connectNulls` bridges the hole with the same
|
|
177
|
+
* straight segment an interpolation would have drawn — without claiming a measurement.
|
|
178
|
+
*/
|
|
179
|
+
declare function mergeSeries(sources: SeriesSource[]): Record<string, number>[];
|
|
180
|
+
/**
|
|
181
|
+
* A series key built out of parts — as a CSS identifier.
|
|
182
|
+
*
|
|
183
|
+
* The chart shell paints each series through `--color-<key>`, and skips (with a
|
|
184
|
+
* console warning) any key that is not `[A-Za-z_][A-Za-z0-9_-]*`. A line whose colour
|
|
185
|
+
* was skipped has no stroke at all: an axis, a grid and nothing between them. So a key
|
|
186
|
+
* assembled out of DATA — an id, an enum, a side — goes through here: every other
|
|
187
|
+
* character becomes `_`, the parts are joined with `_`, and a key that would start with
|
|
188
|
+
* a digit or a hyphen gets a leading `_`.
|
|
189
|
+
*/
|
|
190
|
+
declare function seriesKey(...parts: (string | number)[]): string;
|
|
191
|
+
/**
|
|
192
|
+
* {@link SeriesChart} without the zoom: the same picture, no drag layer, no reset
|
|
193
|
+
* button. For a thumbnail, a print view, or a chart the consumer wraps in its own
|
|
194
|
+
* interaction.
|
|
195
|
+
*/
|
|
196
|
+
declare function StaticSeriesChart({ rows, x, series, axes, valueFormat, height, connectNulls, empty, animationMs, spans, zoom, labels: labelsProp, locale, className, }: SeriesChartProps): react.JSX.Element;
|
|
197
|
+
/**
|
|
198
|
+
* The series chart, zoomable. Drag across the plot to zoom (the drag's shape picks the
|
|
199
|
+
* axes), double-click or press the reset button to go back; wrap several in
|
|
200
|
+
* `SharedXZoom` to move their x windows together.
|
|
201
|
+
*/
|
|
202
|
+
declare const SeriesChart: {
|
|
203
|
+
(props: SeriesChartProps): react.JSX.Element;
|
|
204
|
+
displayName: string;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export { AXIS_TICK_WIDTH, AXIS_TITLE_STRIP, SeriesChart, type SeriesChartAxis, type SeriesChartProps, type SeriesChartSeries, type SeriesChartSpan, type SeriesChartX, type SeriesSource, StaticSeriesChart, axisBandWidth, mergeSeries, oneAxis, padBand, paddedDomain, seriesKey, soleSeriesColor };
|