@seekalfred/primitives 0.6.0 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -15
- package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
- package/dist/activity-heatmap/activity-heatmap.js +699 -0
- package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
- package/dist/activity-heatmap/index.d.ts +2 -0
- package/dist/activity-heatmap/index.js +2 -0
- package/dist/alert/alert.d.ts +53 -4
- package/dist/alert/alert.js +100 -11
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert/index.d.ts +2 -2
- package/dist/alert/index.js +2 -2
- package/dist/alert-dialog/alert-dialog.d.ts +58 -18
- package/dist/alert-dialog/alert-dialog.js +152 -9
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/alert-dialog/index.d.ts +2 -2
- package/dist/alert-dialog/index.js +2 -2
- package/dist/animated-counter/animated-counter.d.ts +38 -0
- package/dist/animated-counter/animated-counter.js +230 -0
- package/dist/animated-counter/animated-counter.js.map +1 -0
- package/dist/animated-counter/index.d.ts +2 -0
- package/dist/animated-counter/index.js +2 -0
- package/dist/announcement-bar/announcement-bar.d.ts +67 -0
- package/dist/announcement-bar/announcement-bar.js +364 -0
- package/dist/announcement-bar/announcement-bar.js.map +1 -0
- package/dist/announcement-bar/index.d.ts +2 -0
- package/dist/announcement-bar/index.js +2 -0
- package/dist/badge/badge.d.ts +1 -1
- package/dist/bar-chart/bar-chart.d.ts +51 -0
- package/dist/bar-chart/bar-chart.js +565 -0
- package/dist/bar-chart/bar-chart.js.map +1 -0
- package/dist/bar-chart/index.d.ts +2 -0
- package/dist/bar-chart/index.js +2 -0
- package/dist/breadcrumb/breadcrumb.d.ts +2 -2
- package/dist/breadcrumb/breadcrumb.js +12 -3
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/brush-chart/brush-chart.d.ts +64 -0
- package/dist/brush-chart/brush-chart.js +1056 -0
- package/dist/brush-chart/brush-chart.js.map +1 -0
- package/dist/brush-chart/index.d.ts +2 -0
- package/dist/brush-chart/index.js +2 -0
- package/dist/button/button.d.ts +1 -1
- package/dist/button/button.js +31 -21
- package/dist/button/button.js.map +1 -1
- package/dist/button-group/button-group.js +3 -4
- package/dist/button-group/button-group.js.map +1 -1
- package/dist/calendar/calendar.d.ts +1 -1
- package/dist/card/card.d.ts +22 -1
- package/dist/card/card.js +60 -7
- package/dist/card/card.js.map +1 -1
- package/dist/card-stack/card-stack.d.ts +54 -0
- package/dist/card-stack/card-stack.js +492 -0
- package/dist/card-stack/card-stack.js.map +1 -0
- package/dist/card-stack/index.d.ts +2 -0
- package/dist/card-stack/index.js +2 -0
- package/dist/carousel/carousel.d.ts +77 -0
- package/dist/carousel/carousel.js +415 -0
- package/dist/carousel/carousel.js.map +1 -0
- package/dist/carousel/index.d.ts +2 -0
- package/dist/carousel/index.js +2 -0
- package/dist/checkbox/checkbox.js +4 -4
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/chip-group/chip-group.d.ts +37 -0
- package/dist/chip-group/chip-group.js +148 -0
- package/dist/chip-group/chip-group.js.map +1 -0
- package/dist/chip-group/index.d.ts +2 -0
- package/dist/chip-group/index.js +2 -0
- package/dist/copy-button/copy-button.d.ts +18 -2
- package/dist/copy-button/copy-button.js +29 -8
- package/dist/copy-button/copy-button.js.map +1 -1
- package/dist/countdown/countdown.d.ts +45 -0
- package/dist/countdown/countdown.js +298 -0
- package/dist/countdown/countdown.js.map +1 -0
- package/dist/countdown/index.d.ts +2 -0
- package/dist/countdown/index.js +2 -0
- package/dist/date-picker/picker-parts.js +1 -1
- package/dist/date-picker/picker-parts.js.map +1 -1
- package/dist/dialog/dialog.js +1 -1
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/donut-chart/donut-chart.d.ts +86 -0
- package/dist/donut-chart/donut-chart.js +593 -0
- package/dist/donut-chart/donut-chart.js.map +1 -0
- package/dist/donut-chart/index.d.ts +2 -0
- package/dist/donut-chart/index.js +2 -0
- package/dist/empty-state/empty-state.d.ts +42 -10
- package/dist/empty-state/empty-state.js +44 -26
- package/dist/empty-state/empty-state.js.map +1 -1
- package/dist/empty-state/index.d.ts +2 -2
- package/dist/empty-state/index.js +2 -2
- package/dist/file-drop-zone/file-drop-zone.js +1 -1
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
- package/dist/filter-bar/filter-bar.d.ts +50 -26
- package/dist/filter-bar/filter-bar.js +317 -157
- package/dist/filter-bar/filter-bar.js.map +1 -1
- package/dist/gauge/gauge.d.ts +45 -0
- package/dist/gauge/gauge.js +219 -0
- package/dist/gauge/gauge.js.map +1 -0
- package/dist/gauge/index.d.ts +2 -0
- package/dist/gauge/index.js +2 -0
- package/dist/in-view-title/in-view-title.d.ts +33 -0
- package/dist/in-view-title/in-view-title.js +119 -0
- package/dist/in-view-title/in-view-title.js.map +1 -0
- package/dist/in-view-title/index.d.ts +2 -0
- package/dist/in-view-title/index.js +2 -0
- package/dist/index.d.ts +53 -10
- package/dist/index.js +52 -12
- package/dist/input/input.js +4 -4
- package/dist/input/input.js.map +1 -1
- package/dist/lib/cn.js +11 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/field-message.js +28 -10
- package/dist/lib/field-message.js.map +1 -1
- package/dist/lib/field-shell.js +10 -3
- package/dist/lib/field-shell.js.map +1 -1
- package/dist/lib/field-surface.js +31 -9
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/flag-emoji.js +50 -0
- package/dist/lib/flag-emoji.js.map +1 -0
- package/dist/lib/flip.js +11 -3
- package/dist/lib/flip.js.map +1 -1
- package/dist/lib/menu-highlight.js +64 -0
- package/dist/lib/menu-highlight.js.map +1 -0
- package/dist/lib/motion.js +9 -2
- package/dist/lib/motion.js.map +1 -1
- package/dist/lib/rolling-value.js +1 -1
- package/dist/lib/rolling-value.js.map +1 -1
- package/dist/lib/spring-tween.js +500 -0
- package/dist/lib/spring-tween.js.map +1 -0
- package/dist/lib/swap-text.js +3 -1
- package/dist/lib/swap-text.js.map +1 -1
- package/dist/lib/use-copy-feedback.js +17 -3
- package/dist/lib/use-copy-feedback.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -0
- package/dist/line-chart/index.js +2 -0
- package/dist/line-chart/line-chart.d.ts +116 -0
- package/dist/line-chart/line-chart.js +740 -0
- package/dist/line-chart/line-chart.js.map +1 -0
- package/dist/mention-input/index.d.ts +2 -0
- package/dist/mention-input/index.js +2 -0
- package/dist/mention-input/mention-input.d.ts +98 -0
- package/dist/mention-input/mention-input.js +576 -0
- package/dist/mention-input/mention-input.js.map +1 -0
- package/dist/menu/menu.d.ts +2 -0
- package/dist/menu/menu.js +23 -12
- package/dist/menu/menu.js.map +1 -1
- package/dist/money-input/index.d.ts +2 -0
- package/dist/money-input/index.js +2 -0
- package/dist/money-input/money-input.d.ts +86 -0
- package/dist/money-input/money-input.js +613 -0
- package/dist/money-input/money-input.js.map +1 -0
- package/dist/morph-select/index.d.ts +2 -0
- package/dist/morph-select/index.js +2 -0
- package/dist/morph-select/morph-select.d.ts +89 -0
- package/dist/morph-select/morph-select.js +712 -0
- package/dist/morph-select/morph-select.js.map +1 -0
- package/dist/multi-select/index.d.ts +2 -0
- package/dist/multi-select/index.js +2 -0
- package/dist/multi-select/multi-select.d.ts +77 -0
- package/dist/multi-select/multi-select.js +209 -0
- package/dist/multi-select/multi-select.js.map +1 -0
- package/dist/number-field/index.d.ts +2 -0
- package/dist/number-field/index.js +2 -0
- package/dist/number-field/number-field.d.ts +42 -0
- package/dist/number-field/number-field.js +199 -0
- package/dist/number-field/number-field.js.map +1 -0
- package/dist/onboarding-checklist/index.d.ts +2 -0
- package/dist/onboarding-checklist/index.js +2 -0
- package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
- package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
- package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
- package/dist/password-field/password-field.d.ts +16 -0
- package/dist/password-field/password-field.js +39 -10
- package/dist/password-field/password-field.js.map +1 -1
- package/dist/phone-input/index.d.ts +3 -0
- package/dist/phone-input/index.js +3 -0
- package/dist/phone-input/phone-countries.d.ts +29 -0
- package/dist/phone-input/phone-countries.js +170 -0
- package/dist/phone-input/phone-countries.js.map +1 -0
- package/dist/phone-input/phone-input.d.ts +66 -0
- package/dist/phone-input/phone-input.js +440 -0
- package/dist/phone-input/phone-input.js.map +1 -0
- package/dist/popover/index.d.ts +2 -2
- package/dist/popover/index.js +2 -2
- package/dist/popover/popover.d.ts +51 -2
- package/dist/popover/popover.js +59 -8
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.d.ts +16 -2
- package/dist/progress/progress.js +16 -9
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/index.d.ts +2 -2
- package/dist/radio-group/index.js +2 -2
- package/dist/radio-group/radio-group.d.ts +33 -3
- package/dist/radio-group/radio-group.js +110 -10
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/reactions/index.d.ts +2 -0
- package/dist/reactions/index.js +2 -0
- package/dist/reactions/reactions.d.ts +50 -0
- package/dist/reactions/reactions.js +315 -0
- package/dist/reactions/reactions.js.map +1 -0
- package/dist/rich-text-editor/index.d.ts +3 -0
- package/dist/rich-text-editor/index.js +2 -0
- package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
- package/dist/rich-text-editor/rich-text-editor.js +909 -0
- package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
- package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
- package/dist/rich-text-editor/rich-text-engine.js +366 -0
- package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
- package/dist/rich-text-editor/rich-text-html.js +174 -0
- package/dist/rich-text-editor/rich-text-html.js.map +1 -0
- package/dist/rich-text-editor/rich-text-icons.js +43 -0
- package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
- package/dist/ridgeline/index.d.ts +2 -0
- package/dist/ridgeline/index.js +2 -0
- package/dist/ridgeline/ridgeline.d.ts +55 -0
- package/dist/ridgeline/ridgeline.js +701 -0
- package/dist/ridgeline/ridgeline.js.map +1 -0
- package/dist/scroll-area/index.d.ts +2 -2
- package/dist/scroll-area/index.js +2 -2
- package/dist/scroll-area/scroll-area.d.ts +68 -31
- package/dist/scroll-area/scroll-area.js +212 -62
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/scrub-input/evaluate.d.ts +14 -0
- package/dist/scrub-input/evaluate.js +72 -0
- package/dist/scrub-input/evaluate.js.map +1 -0
- package/dist/scrub-input/index.d.ts +3 -0
- package/dist/scrub-input/index.js +3 -0
- package/dist/scrub-input/scrub-input.d.ts +74 -0
- package/dist/scrub-input/scrub-input.js +486 -0
- package/dist/scrub-input/scrub-input.js.map +1 -0
- package/dist/search-field/search-field.d.ts +24 -2
- package/dist/search-field/search-field.js +24 -9
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +28 -14
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +22 -10
- package/dist/separator/separator.js +33 -11
- package/dist/separator/separator.js.map +1 -1
- package/dist/sheet/sheet.js +2 -1
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/shortcut-recorder/index.d.ts +3 -0
- package/dist/shortcut-recorder/index.js +3 -0
- package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
- package/dist/shortcut-recorder/shortcut-model.js +206 -0
- package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
- package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
- package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
- package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
- package/dist/slider/index.d.ts +2 -2
- package/dist/slider/slider.d.ts +46 -14
- package/dist/slider/slider.js +151 -67
- package/dist/slider/slider.js.map +1 -1
- package/dist/slope-chart/index.d.ts +2 -0
- package/dist/slope-chart/index.js +2 -0
- package/dist/slope-chart/slope-chart.d.ts +54 -0
- package/dist/slope-chart/slope-chart.js +568 -0
- package/dist/slope-chart/slope-chart.js.map +1 -0
- package/dist/sparkline/index.d.ts +2 -0
- package/dist/sparkline/index.js +2 -0
- package/dist/sparkline/sparkline.d.ts +46 -0
- package/dist/sparkline/sparkline.js +390 -0
- package/dist/sparkline/sparkline.js.map +1 -0
- package/dist/split-bar/index.d.ts +2 -0
- package/dist/split-bar/index.js +2 -0
- package/dist/split-bar/split-bar.d.ts +62 -0
- package/dist/split-bar/split-bar.js +513 -0
- package/dist/split-bar/split-bar.js.map +1 -0
- package/dist/split-button/split-button.d.ts +1 -1
- package/dist/split-button/split-button.js +12 -11
- package/dist/split-button/split-button.js.map +1 -1
- package/dist/stat-card/index.d.ts +2 -2
- package/dist/stat-card/index.js +2 -2
- package/dist/stat-card/stat-card.d.ts +58 -5
- package/dist/stat-card/stat-card.js +144 -17
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/stepper/index.d.ts +2 -0
- package/dist/stepper/index.js +2 -0
- package/dist/stepper/stepper.d.ts +51 -0
- package/dist/stepper/stepper.js +215 -0
- package/dist/stepper/stepper.js.map +1 -0
- package/dist/streamgraph/index.d.ts +2 -0
- package/dist/streamgraph/index.js +2 -0
- package/dist/streamgraph/streamgraph.d.ts +70 -0
- package/dist/streamgraph/streamgraph.js +613 -0
- package/dist/streamgraph/streamgraph.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/swipe-actions/index.d.ts +2 -0
- package/dist/swipe-actions/index.js +2 -0
- package/dist/swipe-actions/swipe-actions.d.ts +67 -0
- package/dist/swipe-actions/swipe-actions.js +552 -0
- package/dist/swipe-actions/swipe-actions.js.map +1 -0
- package/dist/switch/switch.js +2 -2
- package/dist/switch/switch.js.map +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +55 -3
- package/dist/table/table.js +340 -165
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.js +2 -2
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/tag-input/index.d.ts +2 -0
- package/dist/tag-input/index.js +2 -0
- package/dist/tag-input/tag-input.d.ts +40 -0
- package/dist/tag-input/tag-input.js +269 -0
- package/dist/tag-input/tag-input.js.map +1 -0
- package/dist/text-morph/index.d.ts +2 -0
- package/dist/text-morph/index.js +2 -0
- package/dist/text-morph/text-morph.d.ts +23 -0
- package/dist/text-morph/text-morph.js +21 -0
- package/dist/text-morph/text-morph.js.map +1 -0
- package/dist/text-reveal/index.d.ts +2 -0
- package/dist/text-reveal/index.js +2 -0
- package/dist/text-reveal/text-reveal.d.ts +27 -0
- package/dist/text-reveal/text-reveal.js +51 -0
- package/dist/text-reveal/text-reveal.js.map +1 -0
- package/dist/text-shimmer/index.d.ts +2 -0
- package/dist/text-shimmer/index.js +2 -0
- package/dist/text-shimmer/text-shimmer.d.ts +29 -0
- package/dist/text-shimmer/text-shimmer.js +29 -0
- package/dist/text-shimmer/text-shimmer.js.map +1 -0
- package/dist/time-picker/time-picker.d.ts +36 -1
- package/dist/time-picker/time-picker.js +35 -7
- package/dist/time-picker/time-picker.js.map +1 -1
- package/dist/toast/index.d.ts +2 -2
- package/dist/toast/index.js +2 -2
- package/dist/toast/toast.d.ts +7 -1
- package/dist/toast/toast.js +82 -40
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +9 -2
- package/dist/tooltip/tooltip.js +7 -7
- package/dist/tooltip/tooltip.js.map +1 -1
- package/dist/treemap/index.d.ts +2 -0
- package/dist/treemap/index.js +2 -0
- package/dist/treemap/treemap.d.ts +53 -0
- package/dist/treemap/treemap.js +699 -0
- package/dist/treemap/treemap.js.map +1 -0
- package/dist/usage-meter/index.d.ts +2 -0
- package/dist/usage-meter/index.js +2 -0
- package/dist/usage-meter/usage-meter.d.ts +80 -0
- package/dist/usage-meter/usage-meter.js +458 -0
- package/dist/usage-meter/usage-meter.js.map +1 -0
- package/dist/waffle-chart/index.d.ts +2 -0
- package/dist/waffle-chart/index.js +2 -0
- package/dist/waffle-chart/waffle-chart.d.ts +57 -0
- package/dist/waffle-chart/waffle-chart.js +426 -0
- package/dist/waffle-chart/waffle-chart.js.map +1 -0
- package/package.json +165 -3
|
@@ -17,6 +17,21 @@ export interface PasswordFieldProps extends Omit<InputProps, "className" | "styl
|
|
|
17
17
|
* after mount and changed words rise in. Nothing animates on first render.
|
|
18
18
|
*/
|
|
19
19
|
description?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Error text under the field. Sets `aria-invalid`, so the box takes its
|
|
22
|
+
* invalid border, and is announced as an alert when it appears. Opens and
|
|
23
|
+
* closes like `description`; an empty or `undefined` value closes the row.
|
|
24
|
+
* The same prop as Input's. Inside a React Aria `TextField` (which supplies
|
|
25
|
+
* the label and description) the error row is still rendered, after the
|
|
26
|
+
* control, and named in the input's `aria-describedby`.
|
|
27
|
+
*/
|
|
28
|
+
error?: string;
|
|
29
|
+
/**
|
|
30
|
+
* The field must be filled: the input is `required` (announced by screen
|
|
31
|
+
* readers, and checked by native form validation) and the label shows the
|
|
32
|
+
* required marker.
|
|
33
|
+
*/
|
|
34
|
+
isRequired?: boolean;
|
|
20
35
|
/** Translated or reworded toggle names. */
|
|
21
36
|
messages?: Partial<PasswordFieldMessages>;
|
|
22
37
|
}
|
|
@@ -31,6 +46,7 @@ export interface PasswordFieldProps extends Omit<InputProps, "className" | "styl
|
|
|
31
46
|
*
|
|
32
47
|
* @example
|
|
33
48
|
* <PasswordField label="Password" description="At least eight characters." />
|
|
49
|
+
* <PasswordField label="API secret" isRequired error={errors.secret?.message} {...register("secret")} />
|
|
34
50
|
*/
|
|
35
51
|
export declare const PasswordField: import("react").ForwardRefExoticComponent<PasswordFieldProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
36
52
|
//#endregion
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { fieldSurfaceVariants } from "../lib/field-surface.js";
|
|
4
|
+
import { FieldMessage } from "../lib/field-message.js";
|
|
4
5
|
import { FieldShell, useFieldWiring } from "../lib/field-shell.js";
|
|
5
6
|
import { EyeMorph } from "../lib/eye-morph.js";
|
|
6
|
-
import { forwardRef, useState } from "react";
|
|
7
|
-
import { Button, Group, Input } from "react-aria-components";
|
|
8
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { forwardRef, useId, useState } from "react";
|
|
8
|
+
import { Button, Group, Input, InputContext, useSlottedContext } from "react-aria-components";
|
|
9
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
10
|
//#region src/password-field/password-field.tsx
|
|
10
11
|
const defaultPasswordFieldMessages = {
|
|
11
12
|
showPassword: "Show password",
|
|
@@ -22,10 +23,10 @@ const defaultPasswordFieldMessages = {
|
|
|
22
23
|
* would otherwise leave the border pale; a plain Input is different because
|
|
23
24
|
* React Aria treats a text input as always keyboard-focused.
|
|
24
25
|
*/
|
|
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-
|
|
26
|
+
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
27
|
/** The input inside the box: no border, no fill, no outline of its own. */
|
|
27
28
|
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-
|
|
29
|
+
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
30
|
/**
|
|
30
31
|
* A password input with a show and hide toggle. Switching resolves the text in
|
|
31
32
|
* place from a soft blur, and the eye is one outline that a slash draws across.
|
|
@@ -37,8 +38,9 @@ const toggleClass = "field-toggle-button grid size-7.5 flex-none cursor-pointer
|
|
|
37
38
|
*
|
|
38
39
|
* @example
|
|
39
40
|
* <PasswordField label="Password" description="At least eight characters." />
|
|
41
|
+
* <PasswordField label="API secret" isRequired error={errors.secret?.message} {...register("secret")} />
|
|
40
42
|
*/
|
|
41
|
-
const PasswordField = forwardRef(function PasswordField({ label, description, messages: messagesProp, className, ...props }, ref) {
|
|
43
|
+
const PasswordField = forwardRef(function PasswordField({ label, description, error, isRequired, messages: messagesProp, className, ...props }, ref) {
|
|
42
44
|
const messages = {
|
|
43
45
|
...defaultPasswordFieldMessages,
|
|
44
46
|
...messagesProp
|
|
@@ -51,20 +53,35 @@ const PasswordField = forwardRef(function PasswordField({ label, description, me
|
|
|
51
53
|
describedBy: props["aria-describedby"],
|
|
52
54
|
invalid: props["aria-invalid"],
|
|
53
55
|
description,
|
|
54
|
-
error
|
|
56
|
+
error
|
|
55
57
|
});
|
|
58
|
+
const inputContext = useSlottedContext(InputContext);
|
|
59
|
+
const generatedId = useId();
|
|
60
|
+
const insideErrorId = field.inside && error ? `${generatedId}-error` : void 0;
|
|
61
|
+
const [errorRow, setErrorRow] = useState(Boolean(insideErrorId));
|
|
62
|
+
if (insideErrorId && !errorRow) setErrorRow(true);
|
|
63
|
+
const insideProps = insideErrorId ? {
|
|
64
|
+
"aria-invalid": true,
|
|
65
|
+
"aria-describedby": [
|
|
66
|
+
inputContext?.["aria-describedby"],
|
|
67
|
+
props["aria-describedby"],
|
|
68
|
+
insideErrorId
|
|
69
|
+
].filter(Boolean).join(" ")
|
|
70
|
+
} : {};
|
|
56
71
|
const disabled = Boolean(props.disabled);
|
|
57
72
|
const ariaInvalid = props["aria-invalid"];
|
|
58
73
|
const control = /* @__PURE__ */ jsxs(Group, {
|
|
59
74
|
role: "presentation",
|
|
60
75
|
isDisabled: disabled,
|
|
61
|
-
isInvalid: ariaInvalid !== void 0 && ariaInvalid !== false && ariaInvalid !== "false",
|
|
76
|
+
isInvalid: Boolean(error) || ariaInvalid !== void 0 && ariaInvalid !== false && ariaInvalid !== "false",
|
|
62
77
|
"data-slot": "password-field-shell",
|
|
63
78
|
className: shellClass,
|
|
64
79
|
children: [/* @__PURE__ */ jsx(Input, {
|
|
65
80
|
ref,
|
|
66
81
|
...props,
|
|
82
|
+
...isRequired ? { required: true } : {},
|
|
67
83
|
...field.controlProps,
|
|
84
|
+
...insideProps,
|
|
68
85
|
type: visible ? "text" : "password",
|
|
69
86
|
"data-slot": "password-field",
|
|
70
87
|
"data-reveal": toggled ? visible ? "shown" : "hidden" : void 0,
|
|
@@ -82,14 +99,26 @@ const PasswordField = forwardRef(function PasswordField({ label, description, me
|
|
|
82
99
|
children: /* @__PURE__ */ jsx(EyeMorph, { slashed: visible })
|
|
83
100
|
})]
|
|
84
101
|
});
|
|
102
|
+
if (field.inside) return /* @__PURE__ */ jsxs(Fragment$1, { children: [control, errorRow ? /* @__PURE__ */ jsx("div", {
|
|
103
|
+
"data-slot": "password-field-error-row",
|
|
104
|
+
className: "grid min-w-0 font-sans",
|
|
105
|
+
children: /* @__PURE__ */ jsx(FieldMessage, {
|
|
106
|
+
id: insideErrorId,
|
|
107
|
+
text: error,
|
|
108
|
+
tone: "error",
|
|
109
|
+
wordExit: "sharp",
|
|
110
|
+
numbers: false
|
|
111
|
+
})
|
|
112
|
+
}) : null] });
|
|
85
113
|
if (!field.active) return control;
|
|
86
114
|
return /* @__PURE__ */ jsx(FieldShell, {
|
|
87
115
|
label,
|
|
88
116
|
controlId: field.controlId,
|
|
89
117
|
description,
|
|
90
118
|
hintId: field.hintId,
|
|
91
|
-
error
|
|
92
|
-
errorId:
|
|
119
|
+
error,
|
|
120
|
+
errorId: field.errorId,
|
|
121
|
+
required: isRequired,
|
|
93
122
|
wordExit: "sharp",
|
|
94
123
|
numbers: false,
|
|
95
124
|
children: control
|
|
@@ -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-foreground\",\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-md 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,gJACF;;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"}
|
|
1
|
+
{"version":3,"file":"password-field.js","names":["AriaGroup","AriaInput","AriaButton"],"sources":["../../src/password-field/password-field.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, useId, useState } from \"react\";\nimport {\n Button as AriaButton,\n Group as AriaGroup,\n Input as AriaInput,\n InputContext,\n useSlottedContext,\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 { FieldMessage } from \"../lib/field-message.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 /**\n * Error text under the field. Sets `aria-invalid`, so the box takes its\n * invalid border, and is announced as an alert when it appears. Opens and\n * closes like `description`; an empty or `undefined` value closes the row.\n * The same prop as Input's. Inside a React Aria `TextField` (which supplies\n * the label and description) the error row is still rendered, after the\n * control, and named in the input's `aria-describedby`.\n */\n error?: string;\n /**\n * The field must be filled: the input is `required` (announced by screen\n * readers, and checked by native form validation) and the label shows the\n * required marker.\n */\n isRequired?: boolean;\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 * <PasswordField label=\"API secret\" isRequired error={errors.secret?.message} {...register(\"secret\")} />\n */\nexport const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(\n function PasswordField(\n { label, description, error, isRequired, messages: messagesProp, className, ...props },\n ref,\n ) {\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,\n });\n // Inside a React Aria TextField the shell (and its error row) is skipped, since the\n // field supplies the label and description. The error is ours, not the field's,\n // so it is rendered after the control here and added to the input's\n // aria-describedby, after the field's own ids (an explicit prop replaces the\n // context's, so they are carried over). Without this the box turned red with no text.\n const inputContext = useSlottedContext(InputContext) as {\n \"aria-describedby\"?: string;\n } | null;\n const generatedId = useId();\n const insideErrorId = field.inside && error ? `${generatedId}-error` : undefined;\n // The row's wrapper (it carries the font the shell would) is added the first time an\n // error arrives and then kept, so the row can animate closed; without one, nothing is added.\n const [errorRow, setErrorRow] = useState(Boolean(insideErrorId));\n if (insideErrorId && !errorRow) setErrorRow(true);\n const insideProps = insideErrorId\n ? {\n \"aria-invalid\": true,\n \"aria-describedby\": [\n inputContext?.[\"aria-describedby\"],\n props[\"aria-describedby\"],\n insideErrorId,\n ]\n .filter(Boolean)\n .join(\" \"),\n }\n : {};\n const disabled = Boolean(props.disabled);\n const ariaInvalid = props[\"aria-invalid\"];\n const invalid =\n Boolean(error) ||\n (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 {...(isRequired ? { required: true } : {})}\n {...field.controlProps}\n {...insideProps}\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.inside) {\n // Always this shape inside a field, so an error arriving later does not remount the input.\n return (\n <>\n {control}\n {errorRow ? (\n <div data-slot=\"password-field-error-row\" className=\"grid min-w-0 font-sans\">\n <FieldMessage\n id={insideErrorId}\n text={error}\n tone=\"error\"\n wordExit=\"sharp\"\n numbers={false}\n />\n </div>\n ) : null}\n </>\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={error}\n errorId={field.errorId}\n required={isRequired}\n wordExit=\"sharp\"\n numbers={false}\n >\n {control}\n </FieldShell>\n );\n },\n);\n"],"mappings":";;;;;;;;;;AAwBA,MAAa,+BAAsD;CACjE,cAAc;CACd,cAAc;AAChB;;;;;;;;;;;;AAaA,MAAM,aAAa,GACjB,qBAAqB,GACrB,mJACF;;AAGA,MAAM,aACJ;AAEF,MAAM,cACJ;;;;;;;;;;;;;;AAgDF,MAAa,gBAAgB,WAC3B,SAAS,cACP,EAAE,OAAO,aAAa,OAAO,YAAY,UAAU,cAAc,WAAW,GAAG,SAC/E,KACA;CACA,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;CACF,CAAC;CAMD,MAAM,eAAe,kBAAkB,YAAY;CAGnD,MAAM,cAAc,MAAM;CAC1B,MAAM,gBAAgB,MAAM,UAAU,QAAQ,GAAG,YAAY,UAAU,KAAA;CAGvE,MAAM,CAAC,UAAU,eAAe,SAAS,QAAQ,aAAa,CAAC;CAC/D,IAAI,iBAAiB,CAAC,UAAU,YAAY,IAAI;CAChD,MAAM,cAAc,gBAChB;EACE,gBAAgB;EAChB,oBAAoB;GAClB,eAAe;GACf,MAAM;GACN;EACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;CACb,IACA,CAAC;CACL,MAAM,WAAW,QAAQ,MAAM,QAAQ;CACvC,MAAM,cAAc,MAAM;CAK1B,MAAM,UACJ,qBAACA,OAAD;EACE,MAAK;EACL,YAAY;EACZ,WAPF,QAAQ,KAAK,KACZ,gBAAgB,KAAA,KAAa,gBAAgB,SAAS,gBAAgB;EAOrE,aAAU;EACV,WAAW;EALb,UAAA,CAOE,oBAACC,OAAD;GACO;GACL,GAAI;GACJ,GAAK,aAAa,EAAE,UAAU,KAAK,IAAI,CAAC;GACxC,GAAI,MAAM;GACV,GAAI;GACJ,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,MAAM,QAER,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,SACA,WACC,oBAAC,OAAD;EAAK,aAAU;EAA2B,WAAU;EAClD,UAAA,oBAAC,cAAD;GACE,IAAI;GACJ,MAAM;GACN,MAAK;GACL,UAAS;GACT,SAAS;EACV,CAAA;CACE,CAAA,IACH,IACJ,EAAA,CAAA;CAGN,IAAI,CAAC,MAAM,QAAQ,OAAO;CAC1B,OACE,oBAAC,YAAD;EACS;EACP,WAAW,MAAM;EACJ;EACb,QAAQ,MAAM;EACP;EACP,SAAS,MAAM;EACf,UAAU;EACV,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
|