@seekalfred/primitives 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -11
- package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
- package/dist/activity-heatmap/activity-heatmap.js +699 -0
- package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
- package/dist/activity-heatmap/index.d.ts +2 -0
- package/dist/activity-heatmap/index.js +2 -0
- package/dist/alert/alert.d.ts +5 -1
- package/dist/alert/alert.js +68 -7
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert-dialog/alert-dialog.d.ts +45 -18
- package/dist/alert-dialog/alert-dialog.js +128 -9
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/alert-dialog/index.d.ts +2 -2
- package/dist/alert-dialog/index.js +2 -2
- package/dist/animated-counter/animated-counter.d.ts +38 -0
- package/dist/animated-counter/animated-counter.js +230 -0
- package/dist/animated-counter/animated-counter.js.map +1 -0
- package/dist/animated-counter/index.d.ts +2 -0
- package/dist/animated-counter/index.js +2 -0
- package/dist/announcement-bar/announcement-bar.d.ts +67 -0
- package/dist/announcement-bar/announcement-bar.js +364 -0
- package/dist/announcement-bar/announcement-bar.js.map +1 -0
- package/dist/announcement-bar/index.d.ts +2 -0
- package/dist/announcement-bar/index.js +2 -0
- package/dist/badge/badge.d.ts +1 -1
- package/dist/bar-chart/bar-chart.d.ts +51 -0
- package/dist/bar-chart/bar-chart.js +565 -0
- package/dist/bar-chart/bar-chart.js.map +1 -0
- package/dist/bar-chart/index.d.ts +2 -0
- package/dist/bar-chart/index.js +2 -0
- package/dist/breadcrumb/breadcrumb.d.ts +2 -2
- package/dist/breadcrumb/breadcrumb.js +12 -3
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/brush-chart/brush-chart.d.ts +64 -0
- package/dist/brush-chart/brush-chart.js +1056 -0
- package/dist/brush-chart/brush-chart.js.map +1 -0
- package/dist/brush-chart/index.d.ts +2 -0
- package/dist/brush-chart/index.js +2 -0
- package/dist/button/button.d.ts +2 -2
- package/dist/button/button.js +31 -21
- package/dist/button/button.js.map +1 -1
- package/dist/button-group/button-group.js +3 -4
- package/dist/button-group/button-group.js.map +1 -1
- package/dist/card/card.js +1 -1
- package/dist/card/card.js.map +1 -1
- package/dist/card-stack/card-stack.d.ts +54 -0
- package/dist/card-stack/card-stack.js +492 -0
- package/dist/card-stack/card-stack.js.map +1 -0
- package/dist/card-stack/index.d.ts +2 -0
- package/dist/card-stack/index.js +2 -0
- package/dist/carousel/carousel.d.ts +56 -0
- package/dist/carousel/carousel.js +389 -0
- package/dist/carousel/carousel.js.map +1 -0
- package/dist/carousel/index.d.ts +2 -0
- package/dist/carousel/index.js +2 -0
- package/dist/checkbox/checkbox.js +4 -4
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/chip-group/chip-group.d.ts +37 -0
- package/dist/chip-group/chip-group.js +148 -0
- package/dist/chip-group/chip-group.js.map +1 -0
- package/dist/chip-group/index.d.ts +2 -0
- package/dist/chip-group/index.js +2 -0
- package/dist/countdown/countdown.d.ts +45 -0
- package/dist/countdown/countdown.js +298 -0
- package/dist/countdown/countdown.js.map +1 -0
- package/dist/countdown/index.d.ts +2 -0
- package/dist/countdown/index.js +2 -0
- package/dist/date-picker/picker-parts.js +1 -1
- package/dist/date-picker/picker-parts.js.map +1 -1
- package/dist/dialog/dialog.js +1 -1
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/donut-chart/donut-chart.d.ts +64 -0
- package/dist/donut-chart/donut-chart.js +576 -0
- package/dist/donut-chart/donut-chart.js.map +1 -0
- package/dist/donut-chart/index.d.ts +2 -0
- package/dist/donut-chart/index.js +2 -0
- package/dist/empty-state/empty-state.d.ts +21 -10
- package/dist/empty-state/empty-state.js +32 -26
- package/dist/empty-state/empty-state.js.map +1 -1
- package/dist/empty-state/index.d.ts +2 -2
- package/dist/empty-state/index.js +2 -2
- package/dist/file-drop-zone/file-drop-zone.js +1 -1
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
- package/dist/filter-bar/filter-bar.d.ts +47 -26
- package/dist/filter-bar/filter-bar.js +272 -156
- package/dist/filter-bar/filter-bar.js.map +1 -1
- package/dist/gauge/gauge.d.ts +45 -0
- package/dist/gauge/gauge.js +219 -0
- package/dist/gauge/gauge.js.map +1 -0
- package/dist/gauge/index.d.ts +2 -0
- package/dist/gauge/index.js +2 -0
- package/dist/in-view-title/in-view-title.d.ts +33 -0
- package/dist/in-view-title/in-view-title.js +119 -0
- package/dist/in-view-title/in-view-title.js.map +1 -0
- package/dist/in-view-title/index.d.ts +2 -0
- package/dist/in-view-title/index.js +2 -0
- package/dist/index.d.ts +49 -6
- package/dist/index.js +48 -8
- package/dist/lib/cn.js +11 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/field-message.js +28 -10
- package/dist/lib/field-message.js.map +1 -1
- package/dist/lib/field-surface.js +9 -8
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/flag-emoji.js +50 -0
- package/dist/lib/flag-emoji.js.map +1 -0
- package/dist/lib/flip.js +11 -3
- package/dist/lib/flip.js.map +1 -1
- package/dist/lib/menu-highlight.js +64 -0
- package/dist/lib/menu-highlight.js.map +1 -0
- package/dist/lib/motion.js +9 -2
- package/dist/lib/motion.js.map +1 -1
- package/dist/lib/rolling-value.js +1 -1
- package/dist/lib/rolling-value.js.map +1 -1
- package/dist/lib/spring-tween.js +500 -0
- package/dist/lib/spring-tween.js.map +1 -0
- package/dist/lib/swap-text.js +3 -1
- package/dist/lib/swap-text.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -0
- package/dist/line-chart/index.js +2 -0
- package/dist/line-chart/line-chart.d.ts +76 -0
- package/dist/line-chart/line-chart.js +611 -0
- package/dist/line-chart/line-chart.js.map +1 -0
- package/dist/mention-input/index.d.ts +2 -0
- package/dist/mention-input/index.js +2 -0
- package/dist/mention-input/mention-input.d.ts +98 -0
- package/dist/mention-input/mention-input.js +576 -0
- package/dist/mention-input/mention-input.js.map +1 -0
- package/dist/menu/menu.d.ts +2 -0
- package/dist/menu/menu.js +23 -12
- package/dist/menu/menu.js.map +1 -1
- package/dist/money-input/index.d.ts +2 -0
- package/dist/money-input/index.js +2 -0
- package/dist/money-input/money-input.d.ts +65 -0
- package/dist/money-input/money-input.js +607 -0
- package/dist/money-input/money-input.js.map +1 -0
- package/dist/morph-select/index.d.ts +2 -0
- package/dist/morph-select/index.js +2 -0
- package/dist/morph-select/morph-select.d.ts +71 -0
- package/dist/morph-select/morph-select.js +693 -0
- package/dist/morph-select/morph-select.js.map +1 -0
- package/dist/multi-select/index.d.ts +2 -0
- package/dist/multi-select/index.js +2 -0
- package/dist/multi-select/multi-select.d.ts +36 -0
- package/dist/multi-select/multi-select.js +152 -0
- package/dist/multi-select/multi-select.js.map +1 -0
- package/dist/number-field/index.d.ts +2 -0
- package/dist/number-field/index.js +2 -0
- package/dist/number-field/number-field.d.ts +42 -0
- package/dist/number-field/number-field.js +199 -0
- package/dist/number-field/number-field.js.map +1 -0
- package/dist/onboarding-checklist/index.d.ts +2 -0
- package/dist/onboarding-checklist/index.js +2 -0
- package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
- package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
- package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
- package/dist/password-field/password-field.js +2 -2
- package/dist/password-field/password-field.js.map +1 -1
- package/dist/phone-input/index.d.ts +3 -0
- package/dist/phone-input/index.js +3 -0
- package/dist/phone-input/phone-countries.d.ts +29 -0
- package/dist/phone-input/phone-countries.js +170 -0
- package/dist/phone-input/phone-countries.js.map +1 -0
- package/dist/phone-input/phone-input.d.ts +66 -0
- package/dist/phone-input/phone-input.js +440 -0
- package/dist/phone-input/phone-input.js.map +1 -0
- package/dist/popover/popover.d.ts +12 -0
- package/dist/popover/popover.js +31 -7
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.js +1 -1
- package/dist/radio-group/radio-group.js +1 -1
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/reactions/index.d.ts +2 -0
- package/dist/reactions/index.js +2 -0
- package/dist/reactions/reactions.d.ts +50 -0
- package/dist/reactions/reactions.js +315 -0
- package/dist/reactions/reactions.js.map +1 -0
- package/dist/rich-text-editor/index.d.ts +3 -0
- package/dist/rich-text-editor/index.js +2 -0
- package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
- package/dist/rich-text-editor/rich-text-editor.js +909 -0
- package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
- package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
- package/dist/rich-text-editor/rich-text-engine.js +366 -0
- package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
- package/dist/rich-text-editor/rich-text-html.js +174 -0
- package/dist/rich-text-editor/rich-text-html.js.map +1 -0
- package/dist/rich-text-editor/rich-text-icons.js +43 -0
- package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
- package/dist/ridgeline/index.d.ts +2 -0
- package/dist/ridgeline/index.js +2 -0
- package/dist/ridgeline/ridgeline.d.ts +55 -0
- package/dist/ridgeline/ridgeline.js +701 -0
- package/dist/ridgeline/ridgeline.js.map +1 -0
- package/dist/scroll-area/index.d.ts +2 -2
- package/dist/scroll-area/index.js +2 -2
- package/dist/scroll-area/scroll-area.d.ts +68 -31
- package/dist/scroll-area/scroll-area.js +208 -59
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/scrub-input/evaluate.d.ts +14 -0
- package/dist/scrub-input/evaluate.js +72 -0
- package/dist/scrub-input/evaluate.js.map +1 -0
- package/dist/scrub-input/index.d.ts +3 -0
- package/dist/scrub-input/index.js +3 -0
- package/dist/scrub-input/scrub-input.d.ts +74 -0
- package/dist/scrub-input/scrub-input.js +486 -0
- package/dist/scrub-input/scrub-input.js.map +1 -0
- package/dist/search-field/search-field.d.ts +2 -1
- package/dist/search-field/search-field.js +4 -4
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +20 -8
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +1 -1
- package/dist/sheet/sheet.js +2 -1
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/shortcut-recorder/index.d.ts +3 -0
- package/dist/shortcut-recorder/index.js +3 -0
- package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
- package/dist/shortcut-recorder/shortcut-model.js +206 -0
- package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
- package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
- package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
- package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
- package/dist/slider/index.d.ts +2 -2
- package/dist/slider/slider.d.ts +46 -14
- package/dist/slider/slider.js +151 -67
- package/dist/slider/slider.js.map +1 -1
- package/dist/slope-chart/index.d.ts +2 -0
- package/dist/slope-chart/index.js +2 -0
- package/dist/slope-chart/slope-chart.d.ts +54 -0
- package/dist/slope-chart/slope-chart.js +568 -0
- package/dist/slope-chart/slope-chart.js.map +1 -0
- package/dist/sparkline/index.d.ts +2 -0
- package/dist/sparkline/index.js +2 -0
- package/dist/sparkline/sparkline.d.ts +46 -0
- package/dist/sparkline/sparkline.js +390 -0
- package/dist/sparkline/sparkline.js.map +1 -0
- package/dist/split-bar/index.d.ts +2 -0
- package/dist/split-bar/index.js +2 -0
- package/dist/split-bar/split-bar.d.ts +62 -0
- package/dist/split-bar/split-bar.js +513 -0
- package/dist/split-bar/split-bar.js.map +1 -0
- package/dist/split-button/split-button.d.ts +1 -1
- package/dist/split-button/split-button.js +12 -11
- package/dist/split-button/split-button.js.map +1 -1
- package/dist/stat-card/index.d.ts +2 -2
- package/dist/stat-card/index.js +2 -2
- package/dist/stat-card/stat-card.d.ts +24 -4
- package/dist/stat-card/stat-card.js +114 -17
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/stepper/index.d.ts +2 -0
- package/dist/stepper/index.js +2 -0
- package/dist/stepper/stepper.d.ts +51 -0
- package/dist/stepper/stepper.js +215 -0
- package/dist/stepper/stepper.js.map +1 -0
- package/dist/streamgraph/index.d.ts +2 -0
- package/dist/streamgraph/index.js +2 -0
- package/dist/streamgraph/streamgraph.d.ts +70 -0
- package/dist/streamgraph/streamgraph.js +613 -0
- package/dist/streamgraph/streamgraph.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/swipe-actions/index.d.ts +2 -0
- package/dist/swipe-actions/index.js +2 -0
- package/dist/swipe-actions/swipe-actions.d.ts +67 -0
- package/dist/swipe-actions/swipe-actions.js +552 -0
- package/dist/swipe-actions/swipe-actions.js.map +1 -0
- package/dist/switch/switch.js +2 -2
- package/dist/switch/switch.js.map +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +56 -4
- package/dist/table/table.js +340 -165
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.js +2 -2
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/tag-input/index.d.ts +2 -0
- package/dist/tag-input/index.js +2 -0
- package/dist/tag-input/tag-input.d.ts +40 -0
- package/dist/tag-input/tag-input.js +269 -0
- package/dist/tag-input/tag-input.js.map +1 -0
- package/dist/text-morph/index.d.ts +2 -0
- package/dist/text-morph/index.js +2 -0
- package/dist/text-morph/text-morph.d.ts +23 -0
- package/dist/text-morph/text-morph.js +21 -0
- package/dist/text-morph/text-morph.js.map +1 -0
- package/dist/text-reveal/index.d.ts +2 -0
- package/dist/text-reveal/index.js +2 -0
- package/dist/text-reveal/text-reveal.d.ts +27 -0
- package/dist/text-reveal/text-reveal.js +51 -0
- package/dist/text-reveal/text-reveal.js.map +1 -0
- package/dist/text-shimmer/index.d.ts +2 -0
- package/dist/text-shimmer/index.js +2 -0
- package/dist/text-shimmer/text-shimmer.d.ts +29 -0
- package/dist/text-shimmer/text-shimmer.js +29 -0
- package/dist/text-shimmer/text-shimmer.js.map +1 -0
- package/dist/time-picker/time-picker.js +2 -2
- package/dist/time-picker/time-picker.js.map +1 -1
- package/dist/toast/index.d.ts +2 -2
- package/dist/toast/index.js +2 -2
- package/dist/toast/toast.d.ts +6 -0
- package/dist/toast/toast.js +82 -40
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +9 -2
- package/dist/tooltip/tooltip.js +7 -7
- package/dist/tooltip/tooltip.js.map +1 -1
- package/dist/treemap/index.d.ts +2 -0
- package/dist/treemap/index.js +2 -0
- package/dist/treemap/treemap.d.ts +53 -0
- package/dist/treemap/treemap.js +699 -0
- package/dist/treemap/treemap.js.map +1 -0
- package/dist/usage-meter/index.d.ts +2 -0
- package/dist/usage-meter/index.js +2 -0
- package/dist/usage-meter/usage-meter.d.ts +50 -0
- package/dist/usage-meter/usage-meter.js +440 -0
- package/dist/usage-meter/usage-meter.js.map +1 -0
- package/dist/waffle-chart/index.d.ts +2 -0
- package/dist/waffle-chart/index.js +2 -0
- package/dist/waffle-chart/waffle-chart.d.ts +57 -0
- package/dist/waffle-chart/waffle-chart.js +426 -0
- package/dist/waffle-chart/waffle-chart.js.map +1 -0
- package/package.json +165 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboarding-checklist.js","names":[],"sources":["../../src/onboarding-checklist/onboarding-checklist.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useEffect,\n useId,\n useMemo,\n useReducer,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\n\nimport { ActionButton } from \"../action-button/action-button.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { RollingNumber } from \"../lib/field-message.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * OnboardingChecklist: a getting-started card for a new account or workspace,\n * for a short, one-time setup of three to eight loosely ordered steps. (Use a\n * Stepper when the order is strict and steps map to screens.)\n *\n * The progress ring and the count follow every check: the ring fills on the\n * morph spring, the count rolls its digits, and at 100% a tick draws in the\n * ring while the title morphs to `doneTitle`. A checked step fills its circle\n * from the centre, draws its tick and strikes its title through; 720ms later it\n * folds into a \"N completed\" row that opens again, and the next unfinished step\n * opens and takes focus if the finished one had it. A step with an `actionLabel`\n * runs `onAction`: resolving checks the step, throwing an Error keeps it open\n * and shows the message. The body can be hidden and shown again.\n *\n * Rows and panels open and close their height on a spring that never\n * overshoots, closing a little quicker. Nothing animates on first render, and\n * reduced motion removes the height animation and keeps short fades.\n *\n * Built on plain buttons and the shared swap and rolling-number helpers; the\n * action is the library's ActionButton, whose arrow becomes a spinner and then\n * a tick that draws itself. Keyboard: Up and Down walk the step titles,\n * Home and End jump to the first and last, Enter or Space expands a step,\n * toggles its checkbox or runs its action.\n */\nexport interface OnboardingStep {\n id: string;\n title: string;\n description?: string;\n /** Label of the button that does the step. Without it, the step is checked off by hand. */\n actionLabel?: string;\n /** Runs the step. Resolve to check it off; throw an Error to keep it open and show the error's message. */\n onAction?: () => void | Promise<void>;\n}\n\nexport interface OnboardingChecklistProps {\n /** Card title while steps remain. */\n title: string;\n /** Title once every step is done. @default \"You’re all set\" */\n doneTitle?: string;\n /** Steps in order. */\n steps: OnboardingStep[];\n /** Step ids that start completed. */\n defaultCompleted?: string[];\n /** Called with the completed ids, in step order, whenever they change. */\n onCompletedChange?: (completed: string[]) => void;\n /** Starts with the body collapsed. @default false */\n defaultHidden?: boolean;\n /** Called when the Hide or Show button is used. */\n onHiddenChange?: (hidden: boolean) => void;\n /** Extra classes for the card. Merged last. */\n className?: string;\n}\n\ninterface State {\n done: string[];\n settled: string[];\n expanded: string | null;\n errors: Record<string, string>;\n groupOpen: boolean;\n hidden: boolean;\n message: string;\n}\ntype Action =\n | { type: \"check\" | \"uncheck\" | \"settle\" | \"expand\" | \"start\"; id: string; steps: OnboardingStep[] }\n | { type: \"fail\"; id: string; message: string }\n | { type: \"group\" | \"hide\" };\n\n/** How long a checked step stays in place, struck through, before it folds into the completed row. */\nconst SETTLE_MS = 720;\n\nconst ordered = (ids: string[], steps: OnboardingStep[]) =>\n steps.map((step) => step.id).filter((id) => ids.includes(id));\nconst without = (ids: string[], id: string) => ids.filter((item) => item !== id);\nconst omit = (errors: Record<string, string>, id: string) =>\n Object.fromEntries(Object.entries(errors).filter(([key]) => key !== id));\nconst titleOf = (steps: OnboardingStep[], id: string) =>\n steps.find((step) => step.id === id)?.title ?? \"Step\";\n\nfunction init({\n steps,\n completed,\n hidden,\n}: {\n steps: OnboardingStep[];\n completed: string[];\n hidden: boolean;\n}): State {\n const done = ordered(completed, steps);\n return {\n done,\n settled: done,\n expanded: steps.find((step) => !done.includes(step.id))?.id ?? null,\n errors: {},\n groupOpen: false,\n hidden,\n message: \"\",\n };\n}\n\nfunction reducer(state: State, action: Action): State {\n switch (action.type) {\n case \"check\": {\n if (state.done.includes(action.id)) return state;\n const done = ordered([...state.done, action.id], action.steps);\n const total = action.steps.length;\n return {\n ...state,\n done,\n errors: omit(state.errors, action.id),\n message:\n done.length === total\n ? `All ${total} steps done.`\n : `${titleOf(action.steps, action.id)} done. ${done.length} of ${total} complete.`,\n };\n }\n case \"uncheck\": {\n if (!state.done.includes(action.id)) return state;\n const done = without(state.done, action.id);\n return {\n ...state,\n done,\n settled: without(state.settled, action.id),\n expanded: state.expanded ?? action.id,\n message: `${titleOf(action.steps, action.id)} marked as not done. ${done.length} of ${action.steps.length} complete.`,\n };\n }\n case \"settle\": {\n if (!state.done.includes(action.id) || state.settled.includes(action.id)) return state;\n let expanded = state.expanded;\n // The step that just finished hands the open panel to the next unfinished step, wrapping to the top.\n if (expanded === action.id) {\n const order = action.steps.map((step) => step.id);\n const start = order.indexOf(action.id);\n expanded =\n order\n .map((_, offset) => order[(start + offset + 1) % order.length]!)\n .find((id) => !state.done.includes(id)) ?? null;\n }\n return { ...state, settled: ordered([...state.settled, action.id], action.steps), expanded };\n }\n case \"expand\":\n return { ...state, expanded: state.expanded === action.id ? null : action.id };\n case \"start\":\n return { ...state, errors: omit(state.errors, action.id) };\n case \"fail\":\n return {\n ...state,\n errors: { ...state.errors, [action.id]: action.message },\n message: action.message,\n };\n case \"group\":\n return { ...state, groupOpen: !state.groupOpen };\n case \"hide\":\n return { ...state, hidden: !state.hidden };\n }\n}\n\nconst Chevron = () => (\n <svg\n width={16}\n height={16}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n);\n\n/**\n * A box that opens and closes its height (a grid track going between 0fr and 1fr).\n * Closed, it is visibility: hidden once folded, so it leaves the tab order.\n */\nfunction Collapse({ show, children }: { show: boolean; children: ReactNode }) {\n return (\n <div data-shown={show || undefined} className=\"checklist-collapse\">\n <div>{children}</div>\n </div>\n );\n}\n\n/** Every label the slot can show sits invisibly underneath, so a swap never changes the slot's size. */\nfunction SwapLabel({ text, options }: { text: string; options: string[] }) {\n return (\n <span className=\"checklist-swap\" aria-hidden=\"true\">\n {options.map((option, index) => (\n <span key={index} className=\"checklist-swap-sizer\">\n {option}\n </span>\n ))}\n <SwapText swapKey={text} motion=\"rise\" className=\"checklist-swap-item\">\n {text}\n </SwapText>\n </span>\n );\n}\n\n/** The ring's stroke fills on a spring as steps are checked; once every step is done a tick draws in its center. */\nfunction ProgressRing({ done, total }: { done: number; total: number }) {\n const ratio = total ? done / total : 0;\n const complete = total > 0 && done === total;\n return (\n <span\n className=\"checklist-ring\"\n role=\"progressbar\"\n aria-label=\"Setup progress\"\n aria-valuemin={0}\n aria-valuemax={total}\n aria-valuenow={done}\n aria-valuetext={`${done} of ${total} steps done`}\n >\n <svg viewBox=\"0 0 40 40\" fill=\"none\" aria-hidden=\"true\">\n <circle className=\"checklist-ring-track\" cx=\"20\" cy=\"20\" r=\"17\" strokeWidth={3} />\n <g transform=\"rotate(-90 20 20)\">\n <circle\n className=\"checklist-ring-fill\"\n cx=\"20\"\n cy=\"20\"\n r=\"17\"\n strokeWidth={3}\n strokeLinecap=\"round\"\n pathLength={1}\n data-started={ratio > 0 ? \"\" : undefined}\n style={{ \"--ratio\": ratio } as CSSProperties}\n />\n </g>\n </svg>\n <span className=\"checklist-ring-tick\" aria-hidden=\"true\">\n <SwapText swapKey={complete ? \"tick\" : \"\"} motion=\"glyph\" className=\"grid place-items-center\">\n {complete ? (\n <svg viewBox=\"0 0 40 40\" fill=\"none\">\n <path\n className=\"checklist-draw\"\n pathLength={1}\n d=\"M13.5 20.5 18 25l8.5-9.5\"\n stroke=\"currentColor\"\n strokeWidth={2.25}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n ) : null}\n </SwapText>\n </span>\n </span>\n );\n}\n\n/** The fill grows from the center while the tick draws from its short stroke; unchecking retracts both quickly. */\nfunction CheckButton({\n checked,\n labelledBy,\n onToggle,\n}: {\n checked: boolean;\n labelledBy: string;\n onToggle: () => void;\n}) {\n return (\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={checked}\n aria-labelledby={labelledBy}\n className=\"checklist-check\"\n data-check=\"\"\n onClick={onToggle}\n >\n <span className=\"checklist-check-visual\" aria-hidden=\"true\">\n <span className=\"checklist-check-fill\" />\n <svg className=\"checklist-check-mark\" viewBox=\"0 0 22 22\" fill=\"none\">\n <path\n pathLength={1}\n d=\"M6.75 11.25 9.75 14.25 15.25 8.25\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </span>\n </button>\n );\n}\n\ninterface StepItemProps {\n step: OnboardingStep;\n baseId: string;\n done: boolean;\n settled: boolean;\n expanded: boolean;\n error: string | undefined;\n onToggle: () => void;\n onExpand: () => void;\n onRun: () => Promise<void>;\n}\n\nfunction StepItem({\n step,\n baseId,\n done,\n settled,\n expanded,\n error,\n onToggle,\n onExpand,\n onRun,\n}: StepItemProps) {\n const titleId = `${baseId}-${step.id}-title`;\n const panelId = `${baseId}-${step.id}-panel`;\n // The message stays while its slot folds away.\n const [lastError, setLastError] = useState(error);\n if (error && error !== lastError) setLastError(error);\n return (\n <li\n data-step={step.id}\n data-done={done || undefined}\n data-current={expanded || undefined}\n data-open={settled ? undefined : \"\"}\n >\n <Collapse show={!settled}>\n <div className=\"checklist-step-inner\">\n <CheckButton checked={done} labelledBy={titleId} onToggle={onToggle} />\n <div className=\"checklist-step-main\">\n <button\n type=\"button\"\n className=\"checklist-step-title\"\n data-title=\"\"\n aria-expanded={expanded}\n aria-controls={panelId}\n onClick={onExpand}\n >\n <span id={titleId} className=\"checklist-title-text\">\n {step.title}\n </span>\n </button>\n <Collapse show={expanded}>\n <div id={panelId} role=\"region\" aria-labelledby={titleId} className=\"checklist-panel-inner\">\n {step.description ? <p className=\"checklist-description\">{step.description}</p> : null}\n <Collapse show={Boolean(error)}>\n <p role=\"alert\" className=\"checklist-error\">\n {lastError}\n </p>\n </Collapse>\n {step.actionLabel ? (\n // The label stays put through the whole action: only the icon beside it changes.\n <ActionButton\n variant=\"primary\"\n size=\"sm\"\n className=\"mt-3\"\n data-primary=\"\"\n pendingLabel={step.actionLabel}\n successLabel={step.actionLabel}\n onAction={onRun}\n >\n {step.actionLabel}\n </ActionButton>\n ) : null}\n </div>\n </Collapse>\n </div>\n </div>\n </Collapse>\n </li>\n );\n}\n\nexport const OnboardingChecklist = forwardRef<HTMLElement, OnboardingChecklistProps>(\n function OnboardingChecklist(\n {\n title,\n doneTitle = \"You’re all set\",\n steps,\n defaultCompleted = [],\n onCompletedChange,\n defaultHidden = false,\n onHiddenChange,\n className,\n },\n ref,\n ) {\n const baseId = useId();\n const [state, dispatch] = useReducer(\n reducer,\n { steps, completed: defaultCompleted, hidden: defaultHidden },\n init,\n );\n const root = useRef<HTMLElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, root), [ref]);\n const timers = useRef(new Map<string, ReturnType<typeof setTimeout>>());\n const focusRequest = useRef<\"next\" | { id: string } | null>(null);\n const reportCompleted = useRef(onCompletedChange);\n const reported = useRef(state.done);\n\n useEffect(() => {\n reportCompleted.current = onCompletedChange;\n });\n useEffect(() => {\n if (reported.current === state.done) return;\n reported.current = state.done;\n reportCompleted.current?.(state.done);\n }, [state.done]);\n useEffect(() => {\n const pending = timers.current;\n return () => pending.forEach(clearTimeout);\n }, []);\n // Focus moves after the commit that opens its target: the next step's action, or the step reopened from the completed row.\n useEffect(() => {\n const request = focusRequest.current;\n const node = root.current;\n if (!request || !node) return;\n focusRequest.current = null;\n let target: HTMLElement | null;\n if (request === \"next\") {\n const next = state.expanded\n ? node.querySelector<HTMLElement>(`[data-step=\"${state.expanded}\"][data-open]`)\n : null;\n target =\n next?.querySelector<HTMLElement>(\"[data-primary]\") ??\n next?.querySelector<HTMLElement>(\"[data-title]\") ??\n node.querySelector<HTMLElement>(\"[data-group-toggle]\");\n } else {\n target = node.querySelector<HTMLElement>(`[data-step=\"${request.id}\"][data-open] [data-check]`);\n }\n target?.focus({ preventScroll: true });\n });\n\n function check(id: string) {\n dispatch({ type: \"check\", id, steps });\n if (timers.current.has(id)) return;\n timers.current.set(\n id,\n setTimeout(() => {\n timers.current.delete(id);\n if (root.current?.querySelector(`[data-step=\"${id}\"]`)?.contains(document.activeElement))\n focusRequest.current = \"next\";\n dispatch({ type: \"settle\", id, steps });\n }, SETTLE_MS),\n );\n }\n\n function uncheck(id: string, fromGroup: boolean) {\n clearTimeout(timers.current.get(id));\n timers.current.delete(id);\n if (fromGroup) focusRequest.current = { id };\n dispatch({ type: \"uncheck\", id, steps });\n }\n\n async function run(step: OnboardingStep) {\n if (!step.onAction || state.done.includes(step.id)) return;\n dispatch({ type: \"start\", id: step.id, steps });\n try {\n await step.onAction();\n } catch (error) {\n dispatch({\n type: \"fail\",\n id: step.id,\n message:\n error instanceof Error && error.message ? error.message : \"That didn’t work. Try again.\",\n });\n // The button goes back to rest on a rejection.\n throw error;\n }\n check(step.id);\n }\n\n function toggleHidden() {\n dispatch({ type: \"hide\" });\n onHiddenChange?.(!state.hidden);\n }\n\n /** Up and Down walk the step titles, Home and End jump to the first and last. */\n function navigate(event: KeyboardEvent<HTMLUListElement>) {\n const target = event.target as HTMLElement;\n if (!target.matches(\"[data-title]\") || ![\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"].includes(event.key))\n return;\n const titles = Array.from(\n event.currentTarget.querySelectorAll<HTMLElement>(\"li[data-open] [data-title]\"),\n );\n const index = titles.indexOf(target);\n const next =\n event.key === \"Home\"\n ? 0\n : event.key === \"End\"\n ? titles.length - 1\n : (index + (event.key === \"ArrowDown\" ? 1 : -1) + titles.length) % titles.length;\n event.preventDefault();\n titles[next]?.focus();\n }\n\n const total = steps.length;\n const doneCount = state.done.length;\n const allDone = total > 0 && doneCount === total;\n const completed = steps.filter((step) => state.settled.includes(step.id));\n const titleId = `${baseId}-title`;\n const bodyId = `${baseId}-body`;\n const groupId = `${baseId}-completed`;\n\n return (\n <section\n ref={refs}\n aria-labelledby={titleId}\n data-slot=\"onboarding-checklist\"\n data-complete={allDone || undefined}\n data-hidden={state.hidden || undefined}\n className={cn(\"checklist box-border font-sans\", className)}\n >\n <div className=\"checklist-header\">\n <ProgressRing done={doneCount} total={total} />\n <div className=\"checklist-heading\">\n <h3 id={titleId} className=\"checklist-title\">\n <span className=\"sr-only\">{allDone ? doneTitle : title}</span>\n <SwapLabel text={allDone ? doneTitle : title} options={[title, doneTitle]} />\n </h3>\n <p className=\"checklist-count\">\n <span className=\"sr-only\">\n {doneCount} of {total} done\n </span>\n <span className=\"checklist-count-visual\" aria-hidden=\"true\">\n <RollingNumber word={String(doneCount)} />\n of {total} done\n </span>\n </p>\n </div>\n <button\n type=\"button\"\n className=\"checklist-hide\"\n aria-expanded={!state.hidden}\n aria-controls={bodyId}\n onClick={toggleHidden}\n >\n <span className=\"checklist-hide-label\">\n <SwapLabel text={state.hidden ? \"Show\" : \"Hide\"} options={[\"Hide\", \"Show\"]} />\n </span>\n <span className=\"sr-only\">{state.hidden ? \"Show\" : \"Hide\"} checklist</span>\n <span className=\"checklist-chevron\" aria-hidden=\"true\" data-flipped={state.hidden || undefined}>\n <Chevron />\n </span>\n </button>\n </div>\n <div id={bodyId} data-shown={state.hidden ? undefined : \"\"} className=\"checklist-collapse\">\n <div>\n <div className=\"checklist-body-inner\">\n <ul\n className=\"checklist-list\"\n aria-label=\"Steps to do\"\n aria-hidden={completed.length === total ? true : undefined}\n onKeyDown={navigate}\n >\n {steps.map((step) => (\n <StepItem\n key={step.id}\n step={step}\n baseId={baseId}\n done={state.done.includes(step.id)}\n settled={state.settled.includes(step.id)}\n expanded={state.expanded === step.id}\n error={state.errors[step.id]}\n onToggle={() =>\n state.done.includes(step.id) ? uncheck(step.id, false) : check(step.id)\n }\n onExpand={() => dispatch({ type: \"expand\", id: step.id, steps })}\n onRun={() => run(step)}\n />\n ))}\n </ul>\n <Collapse show={completed.length > 0}>\n <button\n type=\"button\"\n className=\"checklist-group-toggle\"\n data-group-toggle=\"\"\n aria-expanded={state.groupOpen}\n aria-controls={groupId}\n onClick={() => dispatch({ type: \"group\" })}\n >\n <span className=\"sr-only\">{completed.length} completed</span>\n <span className=\"checklist-count-visual\" aria-hidden=\"true\">\n <RollingNumber word={String(completed.length)} />\n completed\n </span>\n <span\n className=\"checklist-chevron\"\n aria-hidden=\"true\"\n data-flipped={state.groupOpen ? undefined : \"\"}\n >\n <Chevron />\n </span>\n </button>\n <Collapse show={state.groupOpen}>\n <div id={groupId}>\n <ul className=\"checklist-list\" aria-label=\"Completed steps\">\n {steps.map((step) => {\n const doneId = `${baseId}-${step.id}-done`;\n return (\n <li key={step.id} data-completed-step={step.id} data-done=\"\">\n <Collapse show={state.settled.includes(step.id)}>\n <div className=\"checklist-step-inner\">\n <CheckButton checked labelledBy={doneId} onToggle={() => uncheck(step.id, true)} />\n <span className=\"checklist-done-title\">\n <span id={doneId} className=\"checklist-title-text\">\n {step.title}\n </span>\n </span>\n </div>\n </Collapse>\n </li>\n );\n })}\n </ul>\n </div>\n </Collapse>\n </Collapse>\n </div>\n </div>\n </div>\n <span className=\"sr-only\" role=\"status\">\n {state.message}\n </span>\n </section>\n );\n },\n);\n"],"mappings":";;;;;;;;;;AAyFA,MAAM,YAAY;AAElB,MAAM,WAAW,KAAe,UAC9B,MAAM,KAAK,SAAS,KAAK,EAAE,CAAC,CAAC,QAAQ,OAAO,IAAI,SAAS,EAAE,CAAC;AAC9D,MAAM,WAAW,KAAe,OAAe,IAAI,QAAQ,SAAS,SAAS,EAAE;AAC/E,MAAM,QAAQ,QAAgC,OAC5C,OAAO,YAAY,OAAO,QAAQ,MAAM,CAAC,CAAC,QAAQ,CAAC,SAAS,QAAQ,EAAE,CAAC;AACzE,MAAM,WAAW,OAAyB,OACxC,MAAM,MAAM,SAAS,KAAK,OAAO,EAAE,CAAC,EAAE,SAAS;AAEjD,SAAS,KAAK,EACZ,OACA,WACA,UAKQ;CACR,MAAM,OAAO,QAAQ,WAAW,KAAK;CACrC,OAAO;EACL;EACA,SAAS;EACT,UAAU,MAAM,MAAM,SAAS,CAAC,KAAK,SAAS,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM;EAC/D,QAAQ,CAAC;EACT,WAAW;EACX;EACA,SAAS;CACX;AACF;AAEA,SAAS,QAAQ,OAAc,QAAuB;CACpD,QAAQ,OAAO,MAAf;EACE,KAAK,SAAS;GACZ,IAAI,MAAM,KAAK,SAAS,OAAO,EAAE,GAAG,OAAO;GAC3C,MAAM,OAAO,QAAQ,CAAC,GAAG,MAAM,MAAM,OAAO,EAAE,GAAG,OAAO,KAAK;GAC7D,MAAM,QAAQ,OAAO,MAAM;GAC3B,OAAO;IACL,GAAG;IACH;IACA,QAAQ,KAAK,MAAM,QAAQ,OAAO,EAAE;IACpC,SACE,KAAK,WAAW,QACZ,OAAO,MAAM,gBACb,GAAG,QAAQ,OAAO,OAAO,OAAO,EAAE,EAAE,SAAS,KAAK,OAAO,MAAM,MAAM;GAC7E;EACF;EACA,KAAK,WAAW;GACd,IAAI,CAAC,MAAM,KAAK,SAAS,OAAO,EAAE,GAAG,OAAO;GAC5C,MAAM,OAAO,QAAQ,MAAM,MAAM,OAAO,EAAE;GAC1C,OAAO;IACL,GAAG;IACH;IACA,SAAS,QAAQ,MAAM,SAAS,OAAO,EAAE;IACzC,UAAU,MAAM,YAAY,OAAO;IACnC,SAAS,GAAG,QAAQ,OAAO,OAAO,OAAO,EAAE,EAAE,uBAAuB,KAAK,OAAO,MAAM,OAAO,MAAM,OAAO;GAC5G;EACF;EACA,KAAK,UAAU;GACb,IAAI,CAAC,MAAM,KAAK,SAAS,OAAO,EAAE,KAAK,MAAM,QAAQ,SAAS,OAAO,EAAE,GAAG,OAAO;GACjF,IAAI,WAAW,MAAM;GAErB,IAAI,aAAa,OAAO,IAAI;IAC1B,MAAM,QAAQ,OAAO,MAAM,KAAK,SAAS,KAAK,EAAE;IAChD,MAAM,QAAQ,MAAM,QAAQ,OAAO,EAAE;IACrC,WACE,MACG,KAAK,GAAG,WAAW,OAAO,QAAQ,SAAS,KAAK,MAAM,OAAQ,CAAC,CAC/D,MAAM,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC,KAAK;GACjD;GACA,OAAO;IAAE,GAAG;IAAO,SAAS,QAAQ,CAAC,GAAG,MAAM,SAAS,OAAO,EAAE,GAAG,OAAO,KAAK;IAAG;GAAS;EAC7F;EACA,KAAK,UACH,OAAO;GAAE,GAAG;GAAO,UAAU,MAAM,aAAa,OAAO,KAAK,OAAO,OAAO;EAAG;EAC/E,KAAK,SACH,OAAO;GAAE,GAAG;GAAO,QAAQ,KAAK,MAAM,QAAQ,OAAO,EAAE;EAAE;EAC3D,KAAK,QACH,OAAO;GACL,GAAG;GACH,QAAQ;IAAE,GAAG,MAAM;KAAS,OAAO,KAAK,OAAO;GAAQ;GACvD,SAAS,OAAO;EAClB;EACF,KAAK,SACH,OAAO;GAAE,GAAG;GAAO,WAAW,CAAC,MAAM;EAAU;EACjD,KAAK,QACH,OAAO;GAAE,GAAG;GAAO,QAAQ,CAAC,MAAM;EAAO;CAC7C;AACF;AAEA,MAAM,gBACJ,oBAAC,OAAD;CACE,OAAO;CACP,QAAQ;CACR,SAAQ;CACR,MAAK;CACL,QAAO;CACP,aAAa;CACb,eAAc;CACd,gBAAe;CACf,eAAY;CAEZ,UAAA,oBAAC,QAAD,EAAM,GAAE,iBAAkB,CAAA;AACvB,CAAA;;;;;AAOP,SAAS,SAAS,EAAE,MAAM,YAAoD;CAC5E,OACE,oBAAC,OAAD;EAAK,cAAY,QAAQ,KAAA;EAAW,WAAU;EAC5C,UAAA,oBAAC,OAAD,EAAM,SAAc,CAAA;CACjB,CAAA;AAET;;AAGA,SAAS,UAAU,EAAE,MAAM,WAAgD;CACzE,OACE,qBAAC,QAAD;EAAM,WAAU;EAAiB,eAAY;EAA7C,UAAA,CACG,QAAQ,KAAK,QAAQ,UACpB,oBAAC,QAAD;GAAkB,WAAU;GACzB,UAAA;EACG,GAFK,KAEL,CACP,GACD,oBAAC,UAAD;GAAU,SAAS;GAAM,QAAO;GAAO,WAAU;GAC9C,UAAA;EACO,CAAA,CACN;;AAEV;;AAGA,SAAS,aAAa,EAAE,MAAM,SAA0C;CACtE,MAAM,QAAQ,QAAQ,OAAO,QAAQ;CACrC,MAAM,WAAW,QAAQ,KAAK,SAAS;CACvC,OACE,qBAAC,QAAD;EACE,WAAU;EACV,MAAK;EACL,cAAW;EACX,iBAAe;EACf,iBAAe;EACf,iBAAe;EACf,kBAAgB,GAAG,KAAK,MAAM,MAAM;EAPtC,UAAA,CASE,qBAAC,OAAD;GAAK,SAAQ;GAAY,MAAK;GAAO,eAAY;GAAjD,UAAA,CACE,oBAAC,UAAD;IAAQ,WAAU;IAAuB,IAAG;IAAK,IAAG;IAAK,GAAE;IAAK,aAAa;GAAI,CAAA,GACjF,oBAAC,KAAD;IAAG,WAAU;IACX,UAAA,oBAAC,UAAD;KACE,WAAU;KACV,IAAG;KACH,IAAG;KACH,GAAE;KACF,aAAa;KACb,eAAc;KACd,YAAY;KACZ,gBAAc,QAAQ,IAAI,KAAK,KAAA;KAC/B,OAAO,EAAE,WAAW,MAAM;IAC3B,CAAA;GACA,CAAA,CACA;EACL,CAAA,GAAA,oBAAC,QAAD;GAAM,WAAU;GAAsB,eAAY;GAChD,UAAA,oBAAC,UAAD;IAAU,SAAS,WAAW,SAAS;IAAI,QAAO;IAAQ,WAAU;IACjE,UAAA,WACC,oBAAC,OAAD;KAAK,SAAQ;KAAY,MAAK;KAC5B,UAAA,oBAAC,QAAD;MACE,WAAU;MACV,YAAY;MACZ,GAAE;MACF,QAAO;MACP,aAAa;MACb,eAAc;MACd,gBAAe;KAChB,CAAA;IACE,CAAA,IACH;GACI,CAAA;EACN,CAAA,CACF;;AAEV;;AAGA,SAAS,YAAY,EACnB,SACA,YACA,YAKC;CACD,OACE,oBAAC,UAAD;EACE,MAAK;EACL,MAAK;EACL,gBAAc;EACd,mBAAiB;EACjB,WAAU;EACV,cAAW;EACX,SAAS;EAET,UAAA,qBAAC,QAAD;GAAM,WAAU;GAAyB,eAAY;GAArD,UAAA,CACE,oBAAC,QAAD,EAAM,WAAU,uBAAwB,CAAA,GACxC,oBAAC,OAAD;IAAK,WAAU;IAAuB,SAAQ;IAAY,MAAK;IAC7D,UAAA,oBAAC,QAAD;KACE,YAAY;KACZ,GAAE;KACF,QAAO;KACP,aAAa;KACb,eAAc;KACd,gBAAe;IAChB,CAAA;GACE,CAAA,CACD;;CACA,CAAA;AAEZ;AAcA,SAAS,SAAS,EAChB,MACA,QACA,MACA,SACA,UACA,OACA,UACA,UACA,SACgB;CAChB,MAAM,UAAU,GAAG,OAAO,GAAG,KAAK,GAAG;CACrC,MAAM,UAAU,GAAG,OAAO,GAAG,KAAK,GAAG;CAErC,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,IAAI,SAAS,UAAU,WAAW,aAAa,KAAK;CACpD,OACE,oBAAC,MAAD;EACE,aAAW,KAAK;EAChB,aAAW,QAAQ,KAAA;EACnB,gBAAc,YAAY,KAAA;EAC1B,aAAW,UAAU,KAAA,IAAY;EAEjC,UAAA,oBAAC,UAAD;GAAU,MAAM,CAAC;GACf,UAAA,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,aAAD;KAAa,SAAS;KAAM,YAAY;KAAmB;IAAW,CAAA,GACtE,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,cAAW;MACX,iBAAe;MACf,iBAAe;MACf,SAAS;MAET,UAAA,oBAAC,QAAD;OAAM,IAAI;OAAS,WAAU;OAC1B,UAAA,KAAK;MACF,CAAA;KACA,CAAA,GACR,oBAAC,UAAD;MAAU,MAAM;MACd,UAAA,qBAAC,OAAD;OAAK,IAAI;OAAS,MAAK;OAAS,mBAAiB;OAAS,WAAU;OAApE,UAAA;QACG,KAAK,cAAc,oBAAC,KAAD;SAAG,WAAU;SAAyB,UAAA,KAAK;QAAe,CAAA,IAAI;QAClF,oBAAC,UAAD;SAAU,MAAM,QAAQ,KAAK;SAC3B,UAAA,oBAAC,KAAD;UAAG,MAAK;UAAQ,WAAU;UACvB,UAAA;SACA,CAAA;QACK,CAAA;QACT,KAAK,cAEJ,oBAAC,cAAD;SACE,SAAQ;SACR,MAAK;SACL,WAAU;SACV,gBAAa;SACb,cAAc,KAAK;SACnB,cAAc,KAAK;SACnB,UAAU;SAET,UAAA,KAAK;QACM,CAAA,IACZ;OACD;;KACG,CAAA,CACP;IACF,CAAA,CAAA;;EACG,CAAA;CACR,CAAA;AAER;AAEA,MAAa,sBAAsB,WACjC,SAAS,oBACP,EACE,OACA,YAAY,kBACZ,OACA,mBAAmB,CAAC,GACpB,mBACA,gBAAgB,OAChB,gBACA,aAEF,KACA;CACA,MAAM,SAAS,MAAM;CACrB,MAAM,CAAC,OAAO,YAAY,WACxB,SACA;EAAE;EAAO,WAAW;EAAkB,QAAQ;CAAc,GAC5D,IACF;CACA,MAAM,OAAO,OAA2B,IAAI;CAC5C,MAAM,OAAO,cAAc,UAAU,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC;CACtD,MAAM,SAAS,uBAAO,IAAI,IAA2C,CAAC;CACtE,MAAM,eAAe,OAAuC,IAAI;CAChE,MAAM,kBAAkB,OAAO,iBAAiB;CAChD,MAAM,WAAW,OAAO,MAAM,IAAI;CAElC,gBAAgB;EACd,gBAAgB,UAAU;CAC5B,CAAC;CACD,gBAAgB;EACd,IAAI,SAAS,YAAY,MAAM,MAAM;EACrC,SAAS,UAAU,MAAM;EACzB,gBAAgB,UAAU,MAAM,IAAI;CACtC,GAAG,CAAC,MAAM,IAAI,CAAC;CACf,gBAAgB;EACd,MAAM,UAAU,OAAO;EACvB,aAAa,QAAQ,QAAQ,YAAY;CAC3C,GAAG,CAAC,CAAC;CAEL,gBAAgB;EACd,MAAM,UAAU,aAAa;EAC7B,MAAM,OAAO,KAAK;EAClB,IAAI,CAAC,WAAW,CAAC,MAAM;EACvB,aAAa,UAAU;EACvB,IAAI;EACJ,IAAI,YAAY,QAAQ;GACtB,MAAM,OAAO,MAAM,WACf,KAAK,cAA2B,eAAe,MAAM,SAAS,cAAc,IAC5E;GACJ,SACE,MAAM,cAA2B,gBAAgB,KACjD,MAAM,cAA2B,cAAc,KAC/C,KAAK,cAA2B,qBAAqB;EACzD,OACE,SAAS,KAAK,cAA2B,eAAe,QAAQ,GAAG,2BAA2B;EAEhG,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;CACvC,CAAC;CAED,SAAS,MAAM,IAAY;EACzB,SAAS;GAAE,MAAM;GAAS;GAAI;EAAM,CAAC;EACrC,IAAI,OAAO,QAAQ,IAAI,EAAE,GAAG;EAC5B,OAAO,QAAQ,IACb,IACA,iBAAiB;GACf,OAAO,QAAQ,OAAO,EAAE;GACxB,IAAI,KAAK,SAAS,cAAc,eAAe,GAAG,GAAG,CAAC,EAAE,SAAS,SAAS,aAAa,GACrF,aAAa,UAAU;GACzB,SAAS;IAAE,MAAM;IAAU;IAAI;GAAM,CAAC;EACxC,GAAG,SAAS,CACd;CACF;CAEA,SAAS,QAAQ,IAAY,WAAoB;EAC/C,aAAa,OAAO,QAAQ,IAAI,EAAE,CAAC;EACnC,OAAO,QAAQ,OAAO,EAAE;EACxB,IAAI,WAAW,aAAa,UAAU,EAAE,GAAG;EAC3C,SAAS;GAAE,MAAM;GAAW;GAAI;EAAM,CAAC;CACzC;CAEA,eAAe,IAAI,MAAsB;EACvC,IAAI,CAAC,KAAK,YAAY,MAAM,KAAK,SAAS,KAAK,EAAE,GAAG;EACpD,SAAS;GAAE,MAAM;GAAS,IAAI,KAAK;GAAI;EAAM,CAAC;EAC9C,IAAI;GACF,MAAM,KAAK,SAAS;EACtB,SAAS,OAAO;GACd,SAAS;IACP,MAAM;IACN,IAAI,KAAK;IACT,SACE,iBAAiB,SAAS,MAAM,UAAU,MAAM,UAAU;GAC9D,CAAC;GAED,MAAM;EACR;EACA,MAAM,KAAK,EAAE;CACf;CAEA,SAAS,eAAe;EACtB,SAAS,EAAE,MAAM,OAAO,CAAC;EACzB,iBAAiB,CAAC,MAAM,MAAM;CAChC;;CAGA,SAAS,SAAS,OAAwC;EACxD,MAAM,SAAS,MAAM;EACrB,IAAI,CAAC,OAAO,QAAQ,cAAc,KAAK,CAAC;GAAC;GAAa;GAAW;GAAQ;EAAK,CAAC,CAAC,SAAS,MAAM,GAAG,GAChG;EACF,MAAM,SAAS,MAAM,KACnB,MAAM,cAAc,iBAA8B,4BAA4B,CAChF;EACA,MAAM,QAAQ,OAAO,QAAQ,MAAM;EACnC,MAAM,OACJ,MAAM,QAAQ,SACV,IACA,MAAM,QAAQ,QACZ,OAAO,SAAS,KACf,SAAS,MAAM,QAAQ,cAAc,IAAI,MAAM,OAAO,UAAU,OAAO;EAChF,MAAM,eAAe;EACrB,OAAO,KAAK,EAAE,MAAM;CACtB;CAEA,MAAM,QAAQ,MAAM;CACpB,MAAM,YAAY,MAAM,KAAK;CAC7B,MAAM,UAAU,QAAQ,KAAK,cAAc;CAC3C,MAAM,YAAY,MAAM,QAAQ,SAAS,MAAM,QAAQ,SAAS,KAAK,EAAE,CAAC;CACxE,MAAM,UAAU,GAAG,OAAO;CAC1B,MAAM,SAAS,GAAG,OAAO;CACzB,MAAM,UAAU,GAAG,OAAO;CAE1B,OACE,qBAAC,WAAD;EACE,KAAK;EACL,mBAAiB;EACjB,aAAU;EACV,iBAAe,WAAW,KAAA;EAC1B,eAAa,MAAM,UAAU,KAAA;EAC7B,WAAW,GAAG,kCAAkC,SAAS;EAN3D,UAAA;GAQE,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,cAAD;MAAc,MAAM;MAAkB;KAAQ,CAAA;KAC9C,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,qBAAC,MAAD;OAAI,IAAI;OAAS,WAAU;OAA3B,UAAA,CACE,oBAAC,QAAD;QAAM,WAAU;QAAW,UAAA,UAAU,YAAY;OAAY,CAAA,GAC7D,oBAAC,WAAD;QAAW,MAAM,UAAU,YAAY;QAAO,SAAS,CAAC,OAAO,SAAS;OAAI,CAAA,CAC1E;MACJ,CAAA,GAAA,qBAAC,KAAD;OAAG,WAAU;OAAb,UAAA,CACE,qBAAC,QAAD;QAAM,WAAU;QAAhB,UAAA;SACG;SAAU;SAAK;SAAM;QAClB;OACN,CAAA,GAAA,qBAAC,QAAD;QAAM,WAAU;QAAyB,eAAY;QAArD,UAAA;SACE,oBAAC,eAAD,EAAe,MAAM,OAAO,SAAS,EAAI,CAAA;SAAC;SAChC;SAAM;QACZ;OACL,CAAA,CAAA;MACA,CAAA,CAAA;;KACL,qBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,iBAAe,CAAC,MAAM;MACtB,iBAAe;MACf,SAAS;MALX,UAAA;OAOE,oBAAC,QAAD;QAAM,WAAU;QACd,UAAA,oBAAC,WAAD;SAAW,MAAM,MAAM,SAAS,SAAS;SAAQ,SAAS,CAAC,QAAQ,MAAM;QAAI,CAAA;OACzE,CAAA;OACN,qBAAC,QAAD;QAAM,WAAU;QAAhB,UAAA,CAA2B,MAAM,SAAS,SAAS,QAAO,YAAgB;;OAC1E,oBAAC,QAAD;QAAM,WAAU;QAAoB,eAAY;QAAO,gBAAc,MAAM,UAAU,KAAA;QACnF,UAAA,oBAAC,SAAD,CAAU,CAAA;OACN,CAAA;MACA;;IACL;;GACL,oBAAC,OAAD;IAAK,IAAI;IAAQ,cAAY,MAAM,SAAS,KAAA,IAAY;IAAI,WAAU;IACpE,UAAA,oBAAC,OAAD,EAAA,UACE,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,MAAD;MACE,WAAU;MACV,cAAW;MACX,eAAa,UAAU,WAAW,QAAQ,OAAO,KAAA;MACjD,WAAW;MAEV,UAAA,MAAM,KAAK,SACV,oBAAC,UAAD;OAEQ;OACE;OACR,MAAM,MAAM,KAAK,SAAS,KAAK,EAAE;OACjC,SAAS,MAAM,QAAQ,SAAS,KAAK,EAAE;OACvC,UAAU,MAAM,aAAa,KAAK;OAClC,OAAO,MAAM,OAAO,KAAK;OACzB,gBACE,MAAM,KAAK,SAAS,KAAK,EAAE,IAAI,QAAQ,KAAK,IAAI,KAAK,IAAI,MAAM,KAAK,EAAE;OAExE,gBAAgB,SAAS;QAAE,MAAM;QAAU,IAAI,KAAK;QAAI;OAAM,CAAC;OAC/D,aAAa,IAAI,IAAI;MACtB,GAZM,KAAK,EAYX,CACF;KACC,CAAA,GACJ,qBAAC,UAAD;MAAU,MAAM,UAAU,SAAS;MAAnC,UAAA,CACE,qBAAC,UAAD;OACE,MAAK;OACL,WAAU;OACV,qBAAkB;OAClB,iBAAe,MAAM;OACrB,iBAAe;OACf,eAAe,SAAS,EAAE,MAAM,QAAQ,CAAC;OAN3C,UAAA;QAQE,qBAAC,QAAD;SAAM,WAAU;SAAhB,UAAA,CAA2B,UAAU,QAAO,YAAgB;;QAC5D,qBAAC,QAAD;SAAM,WAAU;SAAyB,eAAY;SAArD,UAAA,CACE,oBAAC,eAAD,EAAe,MAAM,OAAO,UAAU,MAAM,EAAI,CAAA,GAAC,eAE7C;;QACN,oBAAC,QAAD;SACE,WAAU;SACV,eAAY;SACZ,gBAAc,MAAM,YAAY,KAAA,IAAY;SAE5C,UAAA,oBAAC,SAAD,CAAU,CAAA;QACN,CAAA;OACA;MACR,CAAA,GAAA,oBAAC,UAAD;OAAU,MAAM,MAAM;OACpB,UAAA,oBAAC,OAAD;QAAK,IAAI;QACP,UAAA,oBAAC,MAAD;SAAI,WAAU;SAAiB,cAAW;SACvC,UAAA,MAAM,KAAK,SAAS;UACnB,MAAM,SAAS,GAAG,OAAO,GAAG,KAAK,GAAG;UACpC,OACE,oBAAC,MAAD;WAAkB,uBAAqB,KAAK;WAAI,aAAU;WACxD,UAAA,oBAAC,UAAD;YAAU,MAAM,MAAM,QAAQ,SAAS,KAAK,EAAE;YAC5C,UAAA,qBAAC,OAAD;aAAK,WAAU;aAAf,UAAA,CACE,oBAAC,aAAD;cAAa,SAAA;cAAQ,YAAY;cAAQ,gBAAgB,QAAQ,KAAK,IAAI,IAAI;aAAI,CAAA,GAClF,oBAAC,QAAD;cAAM,WAAU;cACd,UAAA,oBAAC,QAAD;eAAM,IAAI;eAAQ,WAAU;eACzB,UAAA,KAAK;cACF,CAAA;aACF,CAAA,CACH;;WACG,CAAA;UACR,GAXK,KAAK,EAWV;SAER,CAAC;QACC,CAAA;OACD,CAAA;MACG,CAAA,CACF;KACP,CAAA,CAAA;IACF,CAAA,EAAA,CAAA;GACF,CAAA;GACL,oBAAC,QAAD;IAAM,WAAU;IAAU,MAAK;IAC5B,UAAA,MAAM;GACH,CAAA;EACC;;AAEb,CACF"}
|
|
@@ -22,10 +22,10 @@ const defaultPasswordFieldMessages = {
|
|
|
22
22
|
* would otherwise leave the border pale; a plain Input is different because
|
|
23
23
|
* React Aria treats a text input as always keyboard-focused.
|
|
24
24
|
*/
|
|
25
|
-
const shellClass = cn(fieldSurfaceVariants(), "flex items-center h-(--density-control-h-md) ps-(--density-control-pad-md) field-toggle-shell [&:where([data-focus-within])]:border-
|
|
25
|
+
const shellClass = cn(fieldSurfaceVariants(), "flex items-center h-(--density-control-h-md) ps-(--density-control-pad-md) field-toggle-shell [&:where([data-focus-within])]:border-border-strong");
|
|
26
26
|
/** The input inside the box: no border, no fill, no outline of its own. */
|
|
27
27
|
const inputClass = "min-w-0 flex-1 border-0 bg-transparent p-0 font-sans text-base text-foreground outline-none placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground data-[disabled]:cursor-not-allowed motion-safe:data-[reveal=shown]:animate-field-reveal-shown motion-safe:data-[reveal=hidden]:animate-field-reveal-hidden";
|
|
28
|
-
const toggleClass = "field-toggle-button grid size-7.5 flex-none cursor-pointer place-items-center rounded-
|
|
28
|
+
const toggleClass = "field-toggle-button grid size-7.5 flex-none cursor-pointer place-items-center rounded-control border-0 bg-transparent p-0 text-muted-foreground outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground aria-pressed:text-foreground data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring) data-[disabled]:cursor-not-allowed";
|
|
29
29
|
/**
|
|
30
30
|
* A password input with a show and hide toggle. Switching resolves the text in
|
|
31
31
|
* place from a soft blur, and the eye is one outline that a slash draws across.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-field.js","names":["AriaGroup","AriaInput","AriaButton"],"sources":["../../src/password-field/password-field.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, useState } from \"react\";\nimport {\n Button as AriaButton,\n Group as AriaGroup,\n Input as AriaInput,\n type InputProps as AriaInputProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { EyeMorph } from \"../lib/eye-morph.js\";\nimport { FieldShell, useFieldWiring } from \"../lib/field-shell.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\n\n/** The toggle's accessible names. Both have English defaults; pass the ones you translate. */\nexport interface PasswordFieldMessages {\n showPassword: string;\n hidePassword: string;\n}\n\nexport const defaultPasswordFieldMessages: PasswordFieldMessages = {\n showPassword: \"Show password\",\n hidePassword: \"Hide password\",\n};\n\n/**\n * The field's box. A React Aria `Group` rather than the input, because the\n * toggle sits inside the same border: it carries the shared field surface (the\n * 160ms border transition, the darker border on hover and focus, the muted\n * disabled look, the invalid border) and the input inside is borderless.\n *\n * `data-focus-within` darkens the border too. A Group reports focus-visible\n * from the input modality, so a mouse click into the input, or onto the toggle,\n * would otherwise leave the border pale; a plain Input is different because\n * React Aria treats a text input as always keyboard-focused.\n */\nconst shellClass = cn(\n fieldSurfaceVariants(),\n \"flex items-center h-(--density-control-h-md) ps-(--density-control-pad-md) field-toggle-shell [&:where([data-focus-within])]:border-
|
|
1
|
+
{"version":3,"file":"password-field.js","names":["AriaGroup","AriaInput","AriaButton"],"sources":["../../src/password-field/password-field.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, useState } from \"react\";\nimport {\n Button as AriaButton,\n Group as AriaGroup,\n Input as AriaInput,\n type InputProps as AriaInputProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { EyeMorph } from \"../lib/eye-morph.js\";\nimport { FieldShell, useFieldWiring } from \"../lib/field-shell.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\n\n/** The toggle's accessible names. Both have English defaults; pass the ones you translate. */\nexport interface PasswordFieldMessages {\n showPassword: string;\n hidePassword: string;\n}\n\nexport const defaultPasswordFieldMessages: PasswordFieldMessages = {\n showPassword: \"Show password\",\n hidePassword: \"Hide password\",\n};\n\n/**\n * The field's box. A React Aria `Group` rather than the input, because the\n * toggle sits inside the same border: it carries the shared field surface (the\n * 160ms border transition, the darker border on hover and focus, the muted\n * disabled look, the invalid border) and the input inside is borderless.\n *\n * `data-focus-within` darkens the border too. A Group reports focus-visible\n * from the input modality, so a mouse click into the input, or onto the toggle,\n * would otherwise leave the border pale; a plain Input is different because\n * React Aria treats a text input as always keyboard-focused.\n */\nconst shellClass = cn(\n fieldSurfaceVariants(),\n \"flex items-center h-(--density-control-h-md) ps-(--density-control-pad-md) field-toggle-shell [&:where([data-focus-within])]:border-border-strong\",\n);\n\n/** The input inside the box: no border, no fill, no outline of its own. */\nconst inputClass =\n \"min-w-0 flex-1 border-0 bg-transparent p-0 font-sans text-base text-foreground outline-none placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground data-[disabled]:cursor-not-allowed motion-safe:data-[reveal=shown]:animate-field-reveal-shown motion-safe:data-[reveal=hidden]:animate-field-reveal-hidden\";\n\nconst toggleClass =\n \"field-toggle-button grid size-7.5 flex-none cursor-pointer place-items-center rounded-control border-0 bg-transparent p-0 text-muted-foreground outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground aria-pressed:text-foreground data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring) data-[disabled]:cursor-not-allowed\";\n\nexport interface PasswordFieldProps extends Omit<\n AriaInputProps,\n \"className\" | \"style\" | \"size\" | \"type\"\n> {\n /** Extra classes for the input. Merged last, so they win conflicts with ours. */\n className?: string;\n /** The visible label, tied to the input with `htmlFor`. */\n label: string;\n /**\n * Helper text under the field, named by `aria-describedby`. A string, because\n * the copy is animated word by word: it opens on a spring when it appears\n * after mount and changed words rise in. Nothing animates on first render.\n */\n description?: string;\n /** Translated or reworded toggle names. */\n messages?: Partial<PasswordFieldMessages>;\n}\n\n/**\n * A password input with a show and hide toggle. Switching resolves the text in\n * place from a soft blur, and the eye is one outline that a slash draws across.\n * Native input props apply to the input; `disabled` also disables the toggle,\n * and `aria-invalid` gives the box its invalid border.\n *\n * The toggle's name changes with the state and it is also `aria-pressed`, so a\n * screen reader may announce both (\"Hide password, pressed\").\n *\n * @example\n * <PasswordField label=\"Password\" description=\"At least eight characters.\" />\n */\nexport const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(\n function PasswordField({ label, description, messages: messagesProp, className, ...props }, ref) {\n const messages = { ...defaultPasswordFieldMessages, ...messagesProp };\n const [visible, setVisible] = useState(false);\n // data-reveal only appears after the first toggle, so the animation runs on each change but never on mount.\n const [toggled, setToggled] = useState(false);\n const field = useFieldWiring({\n enabled: true,\n id: props.id,\n describedBy: props[\"aria-describedby\"],\n invalid: props[\"aria-invalid\"],\n description,\n error: undefined,\n });\n const disabled = Boolean(props.disabled);\n const ariaInvalid = props[\"aria-invalid\"];\n const invalid = ariaInvalid !== undefined && ariaInvalid !== false && ariaInvalid !== \"false\";\n\n const control = (\n <AriaGroup\n role=\"presentation\"\n isDisabled={disabled}\n isInvalid={invalid}\n data-slot=\"password-field-shell\"\n className={shellClass}\n >\n <AriaInput\n ref={ref}\n {...props}\n {...field.controlProps}\n type={visible ? \"text\" : \"password\"}\n data-slot=\"password-field\"\n data-reveal={toggled ? (visible ? \"shown\" : \"hidden\") : undefined}\n className={cn(inputClass, className)}\n />\n <AriaButton\n type=\"button\"\n isDisabled={disabled}\n aria-label={visible ? messages.hidePassword : messages.showPassword}\n aria-pressed={visible}\n onPress={() => {\n setVisible((current) => !current);\n setToggled(true);\n }}\n className={toggleClass}\n >\n <EyeMorph slashed={visible} />\n </AriaButton>\n </AriaGroup>\n );\n\n if (!field.active) return control;\n return (\n <FieldShell\n label={label}\n controlId={field.controlId}\n description={description}\n hintId={field.hintId}\n error={undefined}\n errorId={undefined}\n wordExit=\"sharp\"\n numbers={false}\n >\n {control}\n </FieldShell>\n );\n },\n);\n"],"mappings":";;;;;;;;;AAqBA,MAAa,+BAAsD;CACjE,cAAc;CACd,cAAc;AAChB;;;;;;;;;;;;AAaA,MAAM,aAAa,GACjB,qBAAqB,GACrB,mJACF;;AAGA,MAAM,aACJ;AAEF,MAAM,cACJ;;;;;;;;;;;;;AAgCF,MAAa,gBAAgB,WAC3B,SAAS,cAAc,EAAE,OAAO,aAAa,UAAU,cAAc,WAAW,GAAG,SAAS,KAAK;CAC/F,MAAM,WAAW;EAAE,GAAG;EAA8B,GAAG;CAAa;CACpE,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAE5C,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,QAAQ,eAAe;EAC3B,SAAS;EACT,IAAI,MAAM;EACV,aAAa,MAAM;EACnB,SAAS,MAAM;EACf;EACA,OAAO,KAAA;CACT,CAAC;CACD,MAAM,WAAW,QAAQ,MAAM,QAAQ;CACvC,MAAM,cAAc,MAAM;CAG1B,MAAM,UACJ,qBAACA,OAAD;EACE,MAAK;EACL,YAAY;EACZ,WANY,gBAAgB,KAAA,KAAa,gBAAgB,SAAS,gBAAgB;EAOlF,aAAU;EACV,WAAW;EALb,UAAA,CAOE,oBAACC,OAAD;GACO;GACL,GAAI;GACJ,GAAI,MAAM;GACV,MAAM,UAAU,SAAS;GACzB,aAAU;GACV,eAAa,UAAW,UAAU,UAAU,WAAY,KAAA;GACxD,WAAW,GAAG,YAAY,SAAS;EACpC,CAAA,GACD,oBAACC,QAAD;GACE,MAAK;GACL,YAAY;GACZ,cAAY,UAAU,SAAS,eAAe,SAAS;GACvD,gBAAc;GACd,eAAe;IACb,YAAY,YAAY,CAAC,OAAO;IAChC,WAAW,IAAI;GACjB;GACA,WAAW;GAEX,UAAA,oBAAC,UAAD,EAAU,SAAS,QAAU,CAAA;EACnB,CAAA,CACH;;CAGb,IAAI,CAAC,MAAM,QAAQ,OAAO;CAC1B,OACE,oBAAC,YAAD;EACS;EACP,WAAW,MAAM;EACJ;EACb,QAAQ,MAAM;EACd,OAAO,KAAA;EACP,SAAS,KAAA;EACT,UAAS;EACT,SAAS;EAER,UAAA;CACS,CAAA;AAEhB,CACF"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { PHONE_COUNTRIES, PhoneCountry, PhoneStatus, formatNational, formatPhoneNumber, parsePhoneNumber } from "./phone-countries.js";
|
|
2
|
+
import { PhoneInput, PhoneInputDetails, PhoneInputProps } from "./phone-input.js";
|
|
3
|
+
export { PHONE_COUNTRIES, type PhoneCountry, PhoneInput, type PhoneInputDetails, type PhoneInputProps, type PhoneStatus, formatNational, formatPhoneNumber, parsePhoneNumber };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/phone-input/phone-countries.d.ts
|
|
2
|
+
export interface PhoneCountry {
|
|
3
|
+
/** ISO 3166-1 alpha-2 code, such as "US". */
|
|
4
|
+
iso: string;
|
|
5
|
+
name: string;
|
|
6
|
+
/** Calling code without the plus, such as "44". */
|
|
7
|
+
dial: string;
|
|
8
|
+
/** National formats from shortest to longest. `#` is a digit, anything else is a separator. */
|
|
9
|
+
patterns: string[];
|
|
10
|
+
/** A digit people may type before the number itself, such as "0" in "07911 123456". Empty when there is none. */
|
|
11
|
+
trunk: string;
|
|
12
|
+
/** A realistic national number: the placeholder, and the typing guide. */
|
|
13
|
+
example: string;
|
|
14
|
+
/** Area codes that pick this country when several share a calling code, such as Canada inside +1. */
|
|
15
|
+
areaCodes?: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare const PHONE_COUNTRIES: PhoneCountry[];
|
|
18
|
+
/** Formats national digits as typed, keeping a typed trunk prefix: "07911123456" in the UK reads "07911 123456". */
|
|
19
|
+
export declare function formatNational(entry: PhoneCountry, digits: string): string;
|
|
20
|
+
export type PhoneStatus = "empty" | "incomplete" | "valid" | "too-long";
|
|
21
|
+
/** Reads "+44 (0)7911 123456", "0044 7911 123456" or "+14165550123" into a country and national digits. The longest calling code wins. */
|
|
22
|
+
export declare function parsePhoneNumber(input: string, pool?: PhoneCountry[]): {
|
|
23
|
+
country: PhoneCountry;
|
|
24
|
+
national: string;
|
|
25
|
+
} | null;
|
|
26
|
+
/** Formats an E.164 number for display, such as "+44 7400 123456". Returns the input when it cannot be read. */
|
|
27
|
+
export declare function formatPhoneNumber(e164: string): string;
|
|
28
|
+
//#endregion
|
|
29
|
+
//# sourceMappingURL=phone-countries.d.ts.map
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
//#region src/phone-input/phone-countries.ts
|
|
2
|
+
const country = (iso, name, dial, patterns, example, trunk = "0", areaCodes) => ({
|
|
3
|
+
iso,
|
|
4
|
+
name,
|
|
5
|
+
dial,
|
|
6
|
+
patterns: Array.isArray(patterns) ? patterns : [patterns],
|
|
7
|
+
example,
|
|
8
|
+
trunk,
|
|
9
|
+
areaCodes
|
|
10
|
+
});
|
|
11
|
+
const CA_AREA_CODES = "204 226 236 249 250 263 289 306 343 354 365 367 368 382 387 403 416 418 428 431 437 438 450 468 474 506 514 519 548 579 581 584 587 604 613 639 647 672 683 705 709 742 753 778 780 782 807 819 825 867 873 879 902 905".split(" ");
|
|
12
|
+
const PHONE_COUNTRIES = [
|
|
13
|
+
country("US", "United States", "1", "(###) ###-####", "2015550123", "1"),
|
|
14
|
+
country("CA", "Canada", "1", "(###) ###-####", "5065550123", "1", CA_AREA_CODES),
|
|
15
|
+
country("MX", "Mexico", "52", "## #### ####", "2221234567", ""),
|
|
16
|
+
country("BR", "Brazil", "55", ["(##) ####-####", "(##) #####-####"], "11961234567"),
|
|
17
|
+
country("AR", "Argentina", "54", "## ####-####", "1123456789"),
|
|
18
|
+
country("CO", "Colombia", "57", "### ### ####", "3211234567", ""),
|
|
19
|
+
country("CL", "Chile", "56", "# #### ####", "221234567", ""),
|
|
20
|
+
country("PE", "Peru", "51", "### ### ###", "912345678"),
|
|
21
|
+
country("GB", "United Kingdom", "44", "#### ######", "7400123456"),
|
|
22
|
+
country("IE", "Ireland", "353", "## ### ####", "850123456"),
|
|
23
|
+
country("FR", "France", "33", "# ## ## ## ##", "612345678"),
|
|
24
|
+
country("DE", "Germany", "49", ["### #######", "### ########"], "15123456789"),
|
|
25
|
+
country("ES", "Spain", "34", "### ## ## ##", "612345678", ""),
|
|
26
|
+
country("IT", "Italy", "39", ["### ### ###", "### ### ####"], "3123456789", ""),
|
|
27
|
+
country("PT", "Portugal", "351", "### ### ###", "912345678", ""),
|
|
28
|
+
country("NL", "Netherlands", "31", "# ########", "612345678"),
|
|
29
|
+
country("BE", "Belgium", "32", "### ## ## ##", "470123456"),
|
|
30
|
+
country("CH", "Switzerland", "41", "## ### ## ##", "781234567"),
|
|
31
|
+
country("AT", "Austria", "43", ["### #######", "### ########"], "6641234567"),
|
|
32
|
+
country("SE", "Sweden", "46", "## ### ## ##", "701234567"),
|
|
33
|
+
country("NO", "Norway", "47", "### ## ###", "40612345", ""),
|
|
34
|
+
country("DK", "Denmark", "45", "## ## ## ##", "32123456", ""),
|
|
35
|
+
country("FI", "Finland", "358", ["## ### ####", "## ### #####"], "412345678"),
|
|
36
|
+
country("PL", "Poland", "48", "### ### ###", "512345678", ""),
|
|
37
|
+
country("CZ", "Czechia", "420", "### ### ###", "601123456", ""),
|
|
38
|
+
country("GR", "Greece", "30", "### ### ####", "6912345678", ""),
|
|
39
|
+
country("UA", "Ukraine", "380", "## ### ## ##", "501234567"),
|
|
40
|
+
country("TR", "Turkey", "90", "### ### ## ##", "5012345678"),
|
|
41
|
+
country("IL", "Israel", "972", "##-###-####", "502345678"),
|
|
42
|
+
country("AE", "United Arab Emirates", "971", "## ### ####", "501234567"),
|
|
43
|
+
country("SA", "Saudi Arabia", "966", "## ### ####", "512345678"),
|
|
44
|
+
country("EG", "Egypt", "20", "### ### ####", "1001234567"),
|
|
45
|
+
country("NG", "Nigeria", "234", "### ### ####", "8021234567"),
|
|
46
|
+
country("KE", "Kenya", "254", "### ######", "712123456"),
|
|
47
|
+
country("ZA", "South Africa", "27", "## ### ####", "711234567"),
|
|
48
|
+
country("IN", "India", "91", "#####-#####", "8123456789"),
|
|
49
|
+
country("PK", "Pakistan", "92", "### #######", "3012345678"),
|
|
50
|
+
country("CN", "China", "86", "### #### ####", "13123456789"),
|
|
51
|
+
country("JP", "Japan", "81", "##-####-####", "9012345678"),
|
|
52
|
+
country("KR", "South Korea", "82", ["##-###-####", "##-####-####"], "1020000000"),
|
|
53
|
+
country("TW", "Taiwan", "886", "### ### ###", "912345678"),
|
|
54
|
+
country("HK", "Hong Kong", "852", "#### ####", "51234567", ""),
|
|
55
|
+
country("SG", "Singapore", "65", "#### ####", "81234567", ""),
|
|
56
|
+
country("PH", "Philippines", "63", "### ### ####", "9051234567"),
|
|
57
|
+
country("ID", "Indonesia", "62", [
|
|
58
|
+
"###-###-####",
|
|
59
|
+
"###-####-####",
|
|
60
|
+
"###-####-#####"
|
|
61
|
+
], "81234567890"),
|
|
62
|
+
country("TH", "Thailand", "66", "## ### ####", "812345678"),
|
|
63
|
+
country("VN", "Vietnam", "84", "## ### ## ##", "912345678"),
|
|
64
|
+
country("AU", "Australia", "61", "### ### ###", "412345678"),
|
|
65
|
+
country("NZ", "New Zealand", "64", [
|
|
66
|
+
"# ### ####",
|
|
67
|
+
"## ### ####",
|
|
68
|
+
"## #### ####"
|
|
69
|
+
], "211234567")
|
|
70
|
+
];
|
|
71
|
+
const phoneCountryByIso = (iso) => PHONE_COUNTRIES.find((entry) => entry.iso === iso);
|
|
72
|
+
const capacity = (pattern) => pattern.split("#").length - 1;
|
|
73
|
+
const lengthsOf = (entry) => entry.patterns.map(capacity);
|
|
74
|
+
const onlyDigits = (text) => text.replace(/\D/g, "");
|
|
75
|
+
/** Regional indicator letters. Platforms without flag glyphs show the two letters, which still read. */
|
|
76
|
+
const flagOf = (iso) => String.fromCodePoint(...[...iso.toUpperCase()].map((char) => 127462 + char.charCodeAt(0) - 65));
|
|
77
|
+
function splitTrunk(entry, digits) {
|
|
78
|
+
return entry.trunk && digits.startsWith(entry.trunk) ? [entry.trunk, digits.slice(entry.trunk.length)] : ["", digits];
|
|
79
|
+
}
|
|
80
|
+
/** A typed trunk prefix earns its own room; without one the longest valid length is the cap. */
|
|
81
|
+
const capDigits = (entry, digits) => digits.slice(0, splitTrunk(entry, digits)[0].length + Math.max(...lengthsOf(entry)));
|
|
82
|
+
/** Separators only print while digits follow, so a half-typed number never ends in a dangling ") " or "-". */
|
|
83
|
+
function applyPattern(digits, pattern) {
|
|
84
|
+
let out = "";
|
|
85
|
+
let index = 0;
|
|
86
|
+
for (const char of pattern) {
|
|
87
|
+
if (index >= digits.length) break;
|
|
88
|
+
out += char === "#" ? digits[index++] : char;
|
|
89
|
+
}
|
|
90
|
+
return out + digits.slice(index);
|
|
91
|
+
}
|
|
92
|
+
function patternFor(entry, length) {
|
|
93
|
+
return entry.patterns.find((pattern) => capacity(pattern) >= length) ?? entry.patterns[entry.patterns.length - 1];
|
|
94
|
+
}
|
|
95
|
+
/** Formats national digits as typed, keeping a typed trunk prefix: "07911123456" in the UK reads "07911 123456". */
|
|
96
|
+
function formatNational(entry, digits) {
|
|
97
|
+
const [trunk, rest] = splitTrunk(entry, digits);
|
|
98
|
+
const pattern = patternFor(entry, rest.length);
|
|
99
|
+
const body = applyPattern(rest, pattern);
|
|
100
|
+
if (!trunk) return body;
|
|
101
|
+
if (!rest) return trunk;
|
|
102
|
+
return trunk + (pattern.startsWith("#") ? "" : " ") + body;
|
|
103
|
+
}
|
|
104
|
+
function phoneStatus(entry, digits) {
|
|
105
|
+
const length = splitTrunk(entry, digits)[1].length;
|
|
106
|
+
const lengths = lengthsOf(entry);
|
|
107
|
+
if (!length) return "empty";
|
|
108
|
+
if (lengths.includes(length)) return "valid";
|
|
109
|
+
return length > Math.max(...lengths) ? "too-long" : "incomplete";
|
|
110
|
+
}
|
|
111
|
+
/** The number in E.164, such as "+14155550132". Empty when there are no digits. */
|
|
112
|
+
function toE164(entry, digits) {
|
|
113
|
+
const rest = splitTrunk(entry, digits)[1];
|
|
114
|
+
return rest ? `+${entry.dial}${rest}` : "";
|
|
115
|
+
}
|
|
116
|
+
/** Reads "+44 (0)7911 123456", "0044 7911 123456" or "+14165550123" into a country and national digits. The longest calling code wins. */
|
|
117
|
+
function parsePhoneNumber(input, pool = PHONE_COUNTRIES) {
|
|
118
|
+
const trimmed = input.trim();
|
|
119
|
+
let digits = onlyDigits(trimmed);
|
|
120
|
+
if (!trimmed.startsWith("+")) {
|
|
121
|
+
if (!trimmed.startsWith("00")) return null;
|
|
122
|
+
digits = digits.slice(2);
|
|
123
|
+
}
|
|
124
|
+
for (const size of [
|
|
125
|
+
3,
|
|
126
|
+
2,
|
|
127
|
+
1
|
|
128
|
+
]) {
|
|
129
|
+
const dial = digits.slice(0, size);
|
|
130
|
+
const matches = pool.filter((entry) => entry.dial === dial);
|
|
131
|
+
if (!matches.length) continue;
|
|
132
|
+
let rest = digits.slice(size);
|
|
133
|
+
const entry = matches.find((item) => item.areaCodes?.some((code) => rest.startsWith(code))) ?? matches.find((item) => !item.areaCodes) ?? matches[0];
|
|
134
|
+
if (entry.trunk && rest.startsWith(entry.trunk) && rest.length - entry.trunk.length >= Math.min(...lengthsOf(entry))) rest = rest.slice(entry.trunk.length);
|
|
135
|
+
return {
|
|
136
|
+
country: entry,
|
|
137
|
+
national: capDigits(entry, rest)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
/** Formats an E.164 number for display, such as "+44 7400 123456". Returns the input when it cannot be read. */
|
|
143
|
+
function formatPhoneNumber(e164) {
|
|
144
|
+
const parsed = parsePhoneNumber(e164);
|
|
145
|
+
return parsed ? `+${parsed.country.dial} ${formatNational(parsed.country, parsed.national)}` : e164;
|
|
146
|
+
}
|
|
147
|
+
/** Position in the formatted text just after the nth digit. */
|
|
148
|
+
function caretAfterDigits(text, count) {
|
|
149
|
+
if (count <= 0) {
|
|
150
|
+
const first = text.search(/\d/);
|
|
151
|
+
return first < 0 ? text.length : Math.min(first, text.length);
|
|
152
|
+
}
|
|
153
|
+
let seen = 0;
|
|
154
|
+
for (let index = 0; index < text.length; index++) if (/\d/.test(text[index]) && ++seen === count) return index + 1;
|
|
155
|
+
return text.length;
|
|
156
|
+
}
|
|
157
|
+
/** True when a country matches the search text: a name, an ISO code, or the start of a calling code ("+44", "44"). */
|
|
158
|
+
function matchesCountry(entry, query) {
|
|
159
|
+
const needle = query.trim().toLowerCase();
|
|
160
|
+
if (!needle || needle === "+") return true;
|
|
161
|
+
if (/^[+\d\s()-]+$/.test(needle)) {
|
|
162
|
+
const digits = onlyDigits(needle);
|
|
163
|
+
return entry.dial.startsWith(digits) || digits.startsWith(entry.dial);
|
|
164
|
+
}
|
|
165
|
+
return entry.name.toLowerCase().includes(needle) || entry.iso.toLowerCase() === needle;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
export { PHONE_COUNTRIES, capDigits, caretAfterDigits, flagOf, formatNational, formatPhoneNumber, lengthsOf, matchesCountry, onlyDigits, parsePhoneNumber, phoneCountryByIso, phoneStatus, splitTrunk, toE164 };
|
|
169
|
+
|
|
170
|
+
//# sourceMappingURL=phone-countries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone-countries.js","names":[],"sources":["../../src/phone-input/phone-countries.ts"],"sourcesContent":["/*\n * A compact country table and the number maths built on it: no phone-number library and no metadata download.\n * Each entry is a name, a calling code, an ISO code and the national grouping as a pattern, where `#` is a digit and\n * anything else is a separator. Valid lengths are the digit capacities of the patterns. This is formatting help, not\n * carrier-grade validation: it knows how many digits a country usually has, not which prefixes are assigned.\n */\n\nexport interface PhoneCountry {\n /** ISO 3166-1 alpha-2 code, such as \"US\". */\n iso: string;\n name: string;\n /** Calling code without the plus, such as \"44\". */\n dial: string;\n /** National formats from shortest to longest. `#` is a digit, anything else is a separator. */\n patterns: string[];\n /** A digit people may type before the number itself, such as \"0\" in \"07911 123456\". Empty when there is none. */\n trunk: string;\n /** A realistic national number: the placeholder, and the typing guide. */\n example: string;\n /** Area codes that pick this country when several share a calling code, such as Canada inside +1. */\n areaCodes?: string[];\n}\n\nconst country = (\n iso: string,\n name: string,\n dial: string,\n patterns: string | string[],\n example: string,\n trunk = \"0\",\n areaCodes?: string[],\n): PhoneCountry => ({\n iso,\n name,\n dial,\n patterns: Array.isArray(patterns) ? patterns : [patterns],\n example,\n trunk,\n areaCodes,\n});\n\nconst CA_AREA_CODES =\n \"204 226 236 249 250 263 289 306 343 354 365 367 368 382 387 403 416 418 428 431 437 438 450 468 474 506 514 519 548 579 581 584 587 604 613 639 647 672 683 705 709 742 753 778 780 782 807 819 825 867 873 879 902 905\".split(\n \" \",\n );\n\nexport const PHONE_COUNTRIES: PhoneCountry[] = [\n country(\"US\", \"United States\", \"1\", \"(###) ###-####\", \"2015550123\", \"1\"),\n country(\"CA\", \"Canada\", \"1\", \"(###) ###-####\", \"5065550123\", \"1\", CA_AREA_CODES),\n country(\"MX\", \"Mexico\", \"52\", \"## #### ####\", \"2221234567\", \"\"),\n country(\"BR\", \"Brazil\", \"55\", [\"(##) ####-####\", \"(##) #####-####\"], \"11961234567\"),\n country(\"AR\", \"Argentina\", \"54\", \"## ####-####\", \"1123456789\"),\n country(\"CO\", \"Colombia\", \"57\", \"### ### ####\", \"3211234567\", \"\"),\n country(\"CL\", \"Chile\", \"56\", \"# #### ####\", \"221234567\", \"\"),\n country(\"PE\", \"Peru\", \"51\", \"### ### ###\", \"912345678\"),\n country(\"GB\", \"United Kingdom\", \"44\", \"#### ######\", \"7400123456\"),\n country(\"IE\", \"Ireland\", \"353\", \"## ### ####\", \"850123456\"),\n country(\"FR\", \"France\", \"33\", \"# ## ## ## ##\", \"612345678\"),\n country(\"DE\", \"Germany\", \"49\", [\"### #######\", \"### ########\"], \"15123456789\"),\n country(\"ES\", \"Spain\", \"34\", \"### ## ## ##\", \"612345678\", \"\"),\n country(\"IT\", \"Italy\", \"39\", [\"### ### ###\", \"### ### ####\"], \"3123456789\", \"\"),\n country(\"PT\", \"Portugal\", \"351\", \"### ### ###\", \"912345678\", \"\"),\n country(\"NL\", \"Netherlands\", \"31\", \"# ########\", \"612345678\"),\n country(\"BE\", \"Belgium\", \"32\", \"### ## ## ##\", \"470123456\"),\n country(\"CH\", \"Switzerland\", \"41\", \"## ### ## ##\", \"781234567\"),\n country(\"AT\", \"Austria\", \"43\", [\"### #######\", \"### ########\"], \"6641234567\"),\n country(\"SE\", \"Sweden\", \"46\", \"## ### ## ##\", \"701234567\"),\n country(\"NO\", \"Norway\", \"47\", \"### ## ###\", \"40612345\", \"\"),\n country(\"DK\", \"Denmark\", \"45\", \"## ## ## ##\", \"32123456\", \"\"),\n country(\"FI\", \"Finland\", \"358\", [\"## ### ####\", \"## ### #####\"], \"412345678\"),\n country(\"PL\", \"Poland\", \"48\", \"### ### ###\", \"512345678\", \"\"),\n country(\"CZ\", \"Czechia\", \"420\", \"### ### ###\", \"601123456\", \"\"),\n country(\"GR\", \"Greece\", \"30\", \"### ### ####\", \"6912345678\", \"\"),\n country(\"UA\", \"Ukraine\", \"380\", \"## ### ## ##\", \"501234567\"),\n country(\"TR\", \"Turkey\", \"90\", \"### ### ## ##\", \"5012345678\"),\n country(\"IL\", \"Israel\", \"972\", \"##-###-####\", \"502345678\"),\n country(\"AE\", \"United Arab Emirates\", \"971\", \"## ### ####\", \"501234567\"),\n country(\"SA\", \"Saudi Arabia\", \"966\", \"## ### ####\", \"512345678\"),\n country(\"EG\", \"Egypt\", \"20\", \"### ### ####\", \"1001234567\"),\n country(\"NG\", \"Nigeria\", \"234\", \"### ### ####\", \"8021234567\"),\n country(\"KE\", \"Kenya\", \"254\", \"### ######\", \"712123456\"),\n country(\"ZA\", \"South Africa\", \"27\", \"## ### ####\", \"711234567\"),\n country(\"IN\", \"India\", \"91\", \"#####-#####\", \"8123456789\"),\n country(\"PK\", \"Pakistan\", \"92\", \"### #######\", \"3012345678\"),\n country(\"CN\", \"China\", \"86\", \"### #### ####\", \"13123456789\"),\n country(\"JP\", \"Japan\", \"81\", \"##-####-####\", \"9012345678\"),\n country(\"KR\", \"South Korea\", \"82\", [\"##-###-####\", \"##-####-####\"], \"1020000000\"),\n country(\"TW\", \"Taiwan\", \"886\", \"### ### ###\", \"912345678\"),\n country(\"HK\", \"Hong Kong\", \"852\", \"#### ####\", \"51234567\", \"\"),\n country(\"SG\", \"Singapore\", \"65\", \"#### ####\", \"81234567\", \"\"),\n country(\"PH\", \"Philippines\", \"63\", \"### ### ####\", \"9051234567\"),\n country(\"ID\", \"Indonesia\", \"62\", [\"###-###-####\", \"###-####-####\", \"###-####-#####\"], \"81234567890\"),\n country(\"TH\", \"Thailand\", \"66\", \"## ### ####\", \"812345678\"),\n country(\"VN\", \"Vietnam\", \"84\", \"## ### ## ##\", \"912345678\"),\n country(\"AU\", \"Australia\", \"61\", \"### ### ###\", \"412345678\"),\n country(\"NZ\", \"New Zealand\", \"64\", [\"# ### ####\", \"## ### ####\", \"## #### ####\"], \"211234567\"),\n];\n\nexport const phoneCountryByIso = (iso: string) =>\n PHONE_COUNTRIES.find((entry) => entry.iso === iso);\n\nconst capacity = (pattern: string) => pattern.split(\"#\").length - 1;\nexport const lengthsOf = (entry: PhoneCountry) => entry.patterns.map(capacity);\nexport const onlyDigits = (text: string) => text.replace(/\\D/g, \"\");\n\n/** Regional indicator letters. Platforms without flag glyphs show the two letters, which still read. */\nexport const flagOf = (iso: string) =>\n String.fromCodePoint(...[...iso.toUpperCase()].map((char) => 0x1f1e6 + char.charCodeAt(0) - 65));\n\nexport function splitTrunk(entry: PhoneCountry, digits: string): [string, string] {\n return entry.trunk && digits.startsWith(entry.trunk)\n ? [entry.trunk, digits.slice(entry.trunk.length)]\n : [\"\", digits];\n}\n\n/** A typed trunk prefix earns its own room; without one the longest valid length is the cap. */\nexport const capDigits = (entry: PhoneCountry, digits: string) =>\n digits.slice(0, splitTrunk(entry, digits)[0].length + Math.max(...lengthsOf(entry)));\n\n/** Separators only print while digits follow, so a half-typed number never ends in a dangling \") \" or \"-\". */\nfunction applyPattern(digits: string, pattern: string) {\n let out = \"\";\n let index = 0;\n for (const char of pattern) {\n if (index >= digits.length) break;\n out += char === \"#\" ? digits[index++] : char;\n }\n return out + digits.slice(index);\n}\n\nfunction patternFor(entry: PhoneCountry, length: number) {\n return (\n entry.patterns.find((pattern) => capacity(pattern) >= length) ??\n entry.patterns[entry.patterns.length - 1]!\n );\n}\n\n/** Formats national digits as typed, keeping a typed trunk prefix: \"07911123456\" in the UK reads \"07911 123456\". */\nexport function formatNational(entry: PhoneCountry, digits: string) {\n const [trunk, rest] = splitTrunk(entry, digits);\n const pattern = patternFor(entry, rest.length);\n const body = applyPattern(rest, pattern);\n if (!trunk) return body;\n if (!rest) return trunk;\n return trunk + (pattern.startsWith(\"#\") ? \"\" : \" \") + body;\n}\n\nexport type PhoneStatus = \"empty\" | \"incomplete\" | \"valid\" | \"too-long\";\n\nexport function phoneStatus(entry: PhoneCountry, digits: string): PhoneStatus {\n const length = splitTrunk(entry, digits)[1].length;\n const lengths = lengthsOf(entry);\n if (!length) return \"empty\";\n if (lengths.includes(length)) return \"valid\";\n return length > Math.max(...lengths) ? \"too-long\" : \"incomplete\";\n}\n\n/** The number in E.164, such as \"+14155550132\". Empty when there are no digits. */\nexport function toE164(entry: PhoneCountry, digits: string) {\n const rest = splitTrunk(entry, digits)[1];\n return rest ? `+${entry.dial}${rest}` : \"\";\n}\n\n/** Reads \"+44 (0)7911 123456\", \"0044 7911 123456\" or \"+14165550123\" into a country and national digits. The longest calling code wins. */\nexport function parsePhoneNumber(\n input: string,\n pool: PhoneCountry[] = PHONE_COUNTRIES,\n): { country: PhoneCountry; national: string } | null {\n const trimmed = input.trim();\n let digits = onlyDigits(trimmed);\n if (!trimmed.startsWith(\"+\")) {\n if (!trimmed.startsWith(\"00\")) return null;\n digits = digits.slice(2);\n }\n for (const size of [3, 2, 1]) {\n const dial = digits.slice(0, size);\n const matches = pool.filter((entry) => entry.dial === dial);\n if (!matches.length) continue;\n let rest = digits.slice(size);\n const entry =\n matches.find((item) => item.areaCodes?.some((code) => rest.startsWith(code))) ??\n matches.find((item) => !item.areaCodes) ??\n matches[0]!;\n // \"+44 (0)7911...\" carries a trunk digit it should not: drop it when the rest is still a full number without it.\n if (\n entry.trunk &&\n rest.startsWith(entry.trunk) &&\n rest.length - entry.trunk.length >= Math.min(...lengthsOf(entry))\n ) {\n rest = rest.slice(entry.trunk.length);\n }\n return { country: entry, national: capDigits(entry, rest) };\n }\n return null;\n}\n\n/** Formats an E.164 number for display, such as \"+44 7400 123456\". Returns the input when it cannot be read. */\nexport function formatPhoneNumber(e164: string) {\n const parsed = parsePhoneNumber(e164);\n return parsed\n ? `+${parsed.country.dial} ${formatNational(parsed.country, parsed.national)}`\n : e164;\n}\n\n/** Position in the formatted text just after the nth digit. */\nexport function caretAfterDigits(text: string, count: number) {\n if (count <= 0) {\n const first = text.search(/\\d/);\n return first < 0 ? text.length : Math.min(first, text.length);\n }\n let seen = 0;\n for (let index = 0; index < text.length; index++) {\n if (/\\d/.test(text[index]!) && ++seen === count) return index + 1;\n }\n return text.length;\n}\n\n/** True when a country matches the search text: a name, an ISO code, or the start of a calling code (\"+44\", \"44\"). */\nexport function matchesCountry(entry: PhoneCountry, query: string) {\n const needle = query.trim().toLowerCase();\n if (!needle || needle === \"+\") return true;\n if (/^[+\\d\\s()-]+$/.test(needle)) {\n const digits = onlyDigits(needle);\n return entry.dial.startsWith(digits) || digits.startsWith(entry.dial);\n }\n return entry.name.toLowerCase().includes(needle) || entry.iso.toLowerCase() === needle;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { PhoneCountry, PhoneStatus } from "./phone-countries.js";
|
|
2
|
+
import { FocusEvent } from "react";
|
|
3
|
+
//#region src/phone-input/phone-input.d.ts
|
|
4
|
+
/** What `onValueChange` hands over beside the E.164 number. */
|
|
5
|
+
export interface PhoneInputDetails {
|
|
6
|
+
country: PhoneCountry;
|
|
7
|
+
/** The number as shown in the field, without the calling code. */
|
|
8
|
+
formatted: string;
|
|
9
|
+
status: PhoneStatus;
|
|
10
|
+
valid: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* PhoneInput: a phone number with a country picker. The number formats as it is
|
|
14
|
+
* typed (grouping follows the country, the caret stays on the digit it was on, a
|
|
15
|
+
* faint guide shows the rest of the expected shape), a pasted or autofilled
|
|
16
|
+
* international number such as "+44 7400 123456" picks its own country, and the
|
|
17
|
+
* value comes out in E.164. The picker is a searchable list (React Aria
|
|
18
|
+
* Autocomplete) that matches names, ISO codes and calling codes; typing "+" in
|
|
19
|
+
* the number opens it with a calling code search.
|
|
20
|
+
*
|
|
21
|
+
* Formatting and length checks come from a compact country table in this
|
|
22
|
+
* package, not a phone-number library: it knows the usual digit count and
|
|
23
|
+
* grouping of about fifty countries, not which prefixes a carrier has assigned.
|
|
24
|
+
* Use a full library on the server when a number must be a real, assigned line.
|
|
25
|
+
*/
|
|
26
|
+
export interface PhoneInputProps {
|
|
27
|
+
/** What the number is for, and the field's accessible name. */
|
|
28
|
+
label: string;
|
|
29
|
+
/** Keep the label for assistive technology only. */
|
|
30
|
+
labelHidden?: boolean;
|
|
31
|
+
/** The number in E.164, such as "+14155550132". An empty string clears the field. */
|
|
32
|
+
value?: string;
|
|
33
|
+
defaultValue?: string;
|
|
34
|
+
/** Fires on every edit with the E.164 number (empty when there are no digits) and its parsed details. */
|
|
35
|
+
onValueChange?: (value: string, details: PhoneInputDetails) => void;
|
|
36
|
+
/** ISO code of the selected country. Controlled when given. */
|
|
37
|
+
country?: string;
|
|
38
|
+
/** ISO code used until someone picks a country or enters an international number. @default "US" */
|
|
39
|
+
defaultCountry?: string;
|
|
40
|
+
onCountryChange?: (iso: string) => void;
|
|
41
|
+
/** Limit the picker to these ISO codes. */
|
|
42
|
+
countries?: string[];
|
|
43
|
+
/** Helper text under the field. A string, because the copy is animated word by word. */
|
|
44
|
+
description?: string;
|
|
45
|
+
/** Replaces the built-in length message. */
|
|
46
|
+
error?: string;
|
|
47
|
+
/** Show a message after blur when the number is incomplete. @default true */
|
|
48
|
+
validate?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* How a country is marked in the field and the list. `auto` draws the flag emoji where the platform can (Windows
|
|
51
|
+
* cannot) and the two-letter ISO code where it cannot; `emoji` and `code` force one.
|
|
52
|
+
* @default "auto"
|
|
53
|
+
*/
|
|
54
|
+
flags?: "auto" | "emoji" | "code";
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
required?: boolean;
|
|
57
|
+
/** Adds a hidden input carrying the E.164 value for native form submission. */
|
|
58
|
+
name?: string;
|
|
59
|
+
id?: string;
|
|
60
|
+
/** Extra classes for the wrapper. Merged last. */
|
|
61
|
+
className?: string;
|
|
62
|
+
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
|
|
63
|
+
}
|
|
64
|
+
export declare const PhoneInput: import("react").ForwardRefExoticComponent<PhoneInputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
65
|
+
//#endregion
|
|
66
|
+
//# sourceMappingURL=phone-input.d.ts.map
|