@agentero/design-system 0.34.2 → 0.35.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.
@@ -6779,6 +6779,174 @@
6779
6779
  ],
6780
6780
  "import": "import { Field, FieldText, InputPassword, Label } from \"@agentero/design-system\";"
6781
6781
  },
6782
+ "components-debouncedinputsearch": {
6783
+ "id": "components-debouncedinputsearch",
6784
+ "name": "DebouncedInputSearch",
6785
+ "path": "./src/input-search/debounced-input-search.stories.tsx",
6786
+ "jsDocTags": {
6787
+ "summary": [
6788
+ "Search input that reports the query once the user pauses typing"
6789
+ ],
6790
+ "dataAttribute": [
6791
+ "{string} data-slot - Always set to \"input-search\" on the frame"
6792
+ ],
6793
+ "example": [
6794
+ "<DebouncedInputSearch\n aria-label=\"Search agencies\"\n value={filters.query}\n onChange={event => setFilters({ ...filters, query: event.target.value })}\n isLoading={isFetching}\n/>"
6795
+ ]
6796
+ },
6797
+ "description": "DebouncedInputSearch is an `InputSearch` for filters that are expensive to\nrun. Keystrokes show up at once, but `onChange` fires only once the user\npauses for 300ms, with the last change event. It is always controlled:\n`value` and `onChange` are required, and the field follows a `value`\nchanged from outside.",
6798
+ "summary": "Search input that reports the query once the user pauses typing",
6799
+ "reactComponentMeta": {
6800
+ "displayName": "DebouncedInputSearch",
6801
+ "exportName": "DebouncedInputSearch",
6802
+ "filePath": "/home/runner/work/design-system/design-system/src/input-search/debounced-input-search.tsx",
6803
+ "description": "DebouncedInputSearch is an [InputSearch](?path=/docs/components-inputsearch--docs)\nfor filters that are expensive to run: a table query, a request to the\nserver. Keystrokes show up in the field at once, but `onChange` fires only\nonce the user pauses for `DEBOUNCE_TIME` (300ms), with the last change\nevent, so the page runs one search per pause instead of one per key. It is\nalways controlled: `value` and `onChange` are required, the page owns the\nquery, and the field follows a `value` changed from outside while the user\nis not typing.\n\nEvery other prop goes to the InputSearch and from there to the `<input>`:\nthe `ref`, `size`, `isLoading`, `placeholder`, `aria-*` and every other\nstandard attribute. Only `type` is not accepted. `className` styles the\nframe.\n\nDo not use it where every keystroke matters, such as filtering a short list\nalready in memory; that is a plain\n[InputSearch](?path=/docs/components-inputsearch--docs). To pick a value from\nthe results, use [Combobox](?path=/docs/components-combobox--docs).",
6804
+ "jsDocTags": {
6805
+ "summary": [
6806
+ "Search input that reports the query once the user pauses typing"
6807
+ ],
6808
+ "dataAttribute": [
6809
+ "{string} data-slot - Always set to \"input-search\" on the frame"
6810
+ ],
6811
+ "example": [
6812
+ "<DebouncedInputSearch\n aria-label=\"Search agencies\"\n value={filters.query}\n onChange={event => setFilters({ ...filters, query: event.target.value })}\n isLoading={isFetching}\n/>"
6813
+ ]
6814
+ },
6815
+ "props": {
6816
+ "size": {
6817
+ "name": "size",
6818
+ "required": false,
6819
+ "type": {
6820
+ "name": "enum",
6821
+ "raw": "\"sm\" | \"md\" | \"lg\"",
6822
+ "value": [
6823
+ {
6824
+ "value": "\"sm\""
6825
+ },
6826
+ {
6827
+ "value": "\"md\""
6828
+ },
6829
+ {
6830
+ "value": "\"lg\""
6831
+ }
6832
+ ]
6833
+ },
6834
+ "description": "Control height. Defaults to `'md'`.\n- `sm` (32px) — dense layouts where vertical space is tight.\n- `md` (40px) — standard form density.\n- `lg` (48px) — low-density forms and larger touch targets. Also raises\n the text to `base` and the corner radius to `lg`.",
6835
+ "defaultValue": null,
6836
+ "parent": {
6837
+ "name": "InputProps",
6838
+ "fileName": "src/input/input.tsx"
6839
+ },
6840
+ "declarations": [
6841
+ {
6842
+ "name": "TypeLiteral",
6843
+ "fileName": "src/input/input.tsx"
6844
+ }
6845
+ ]
6846
+ },
6847
+ "clearLabel": {
6848
+ "name": "clearLabel",
6849
+ "required": false,
6850
+ "type": {
6851
+ "name": "string"
6852
+ },
6853
+ "description": "Accessible name of the clear button. Defaults to `'Clear search'`; name it\nafter what is searched (`'Clear agency search'`) when a page carries\nseveral search fields.",
6854
+ "defaultValue": null,
6855
+ "parent": {
6856
+ "name": "InputSearchProps",
6857
+ "fileName": "src/input-search/input-search.tsx"
6858
+ },
6859
+ "declarations": [
6860
+ {
6861
+ "name": "TypeLiteral",
6862
+ "fileName": "src/input-search/input-search.tsx"
6863
+ }
6864
+ ]
6865
+ },
6866
+ "isLoading": {
6867
+ "name": "isLoading",
6868
+ "required": false,
6869
+ "type": {
6870
+ "name": "boolean"
6871
+ },
6872
+ "description": "Shows a spinner at the end of the frame, in place of the clear button,\nwhile the results for the current text are on their way. Defaults to\n`false`.",
6873
+ "defaultValue": null,
6874
+ "parent": {
6875
+ "name": "InputSearchProps",
6876
+ "fileName": "src/input-search/input-search.tsx"
6877
+ },
6878
+ "declarations": [
6879
+ {
6880
+ "name": "TypeLiteral",
6881
+ "fileName": "src/input-search/input-search.tsx"
6882
+ }
6883
+ ]
6884
+ },
6885
+ "value": {
6886
+ "name": "value",
6887
+ "required": true,
6888
+ "type": {
6889
+ "name": "string"
6890
+ },
6891
+ "description": "The query the page is filtering by. The field shows it, and follows it\nwhen it changes from outside (a reset button, a saved filter, navigation),\nunless the user is in the middle of typing: then what they typed stays,\nand `onChange` reports it once they pause.",
6892
+ "defaultValue": null,
6893
+ "parent": {
6894
+ "name": "DebouncedInputSearchProps",
6895
+ "fileName": "src/input-search/debounced-input-search.tsx"
6896
+ },
6897
+ "declarations": [
6898
+ {
6899
+ "name": "TypeLiteral",
6900
+ "fileName": "src/input-search/debounced-input-search.tsx"
6901
+ }
6902
+ ]
6903
+ },
6904
+ "onChange": {
6905
+ "name": "onChange",
6906
+ "required": true,
6907
+ "type": {
6908
+ "name": "ChangeEventHandler<HTMLInputElement>"
6909
+ },
6910
+ "description": "Fires once the user has stopped typing for `DEBOUNCE_TIME` milliseconds,\nwith the change event of the last keystroke; clearing the field goes\nthrough the same delay. A pending call fires at once when the user\npresses Enter, and is dropped when the field unmounts, so a page that is\nnavigating away is not sent back. Read the text from `event.target.value`, which is\nwhat the field shows at that moment. `event.currentTarget` is no longer\nset by then, since the event is delivered after the browser's dispatch.",
6911
+ "defaultValue": null,
6912
+ "parent": {
6913
+ "name": "DebouncedInputSearchProps",
6914
+ "fileName": "src/input-search/debounced-input-search.tsx"
6915
+ },
6916
+ "declarations": [
6917
+ {
6918
+ "name": "TypeLiteral",
6919
+ "fileName": "src/input-search/debounced-input-search.tsx"
6920
+ }
6921
+ ]
6922
+ }
6923
+ }
6924
+ },
6925
+ "stories": [
6926
+ {
6927
+ "id": "components-debouncedinputsearch--default",
6928
+ "name": "Default",
6929
+ "snippet": "const Default = () => {\n const [query, setQuery] = useState(args.value);\n\n return (\n <div style={{ display: 'grid', gap: '1rem' }}>\n <DebouncedInputSearch\n aria-label=\"Search agencies\"\n value={query}\n onChange={event => {\n setQuery(event.target.value);\n args.onChange(event);\n }} />\n <output>Filtering by: {query}</output>\n </div>\n );\n};",
6930
+ "description": "A page filtering by a query it owns, shown below the field. Typing shows up\nin the field at once; `onChange` arrives once the user pauses, with the\nwhole text in `event.target.value`, and only then does the query below\nchange. Clearing the field goes through the same delay.",
6931
+ "summary": "Controlled query reported once the user stops typing"
6932
+ },
6933
+ {
6934
+ "id": "components-debouncedinputsearch--external-value",
6935
+ "name": "External Value",
6936
+ "snippet": "const ExternalValue = () => {\n const [query, setQuery] = useState('Acme');\n\n return (\n <div style={{ display: 'grid', gap: '1rem' }}>\n <DebouncedInputSearch\n aria-label=\"Search agencies\"\n value={query}\n onChange={event => {\n setQuery(event.target.value);\n args.onChange(event);\n }} />\n <Button variant=\"secondary\" size=\"sm\" onClick={() => setQuery('Travelers')}>Apply saved search\n </Button>\n <output>Filtering by: {query}</output>\n </div>\n );\n};",
6937
+ "description": "The field follows `value` when the page changes it from outside, here a\nsaved search applied with a button: nothing is being typed, so the new\nquery replaces the text.",
6938
+ "summary": "Field following a value changed from outside while idle"
6939
+ },
6940
+ {
6941
+ "id": "components-debouncedinputsearch--submit-on-enter",
6942
+ "name": "Submit On Enter",
6943
+ "snippet": "const SubmitOnEnter = () => {\n const [query, setQuery] = useState(args.value);\n\n return (\n <div style={{ display: 'grid', gap: '1rem' }}>\n <DebouncedInputSearch\n aria-label=\"Search agencies\"\n value={query}\n onChange={event => {\n setQuery(event.target.value);\n args.onChange(event);\n }} />\n <output>Filtering by: {query}</output>\n </div>\n );\n};",
6944
+ "description": "Pressing Enter reports the text at once instead of waiting for the pause.",
6945
+ "summary": "Enter reports the pending query without waiting"
6946
+ }
6947
+ ],
6948
+ "import": "import { Button, DebouncedInputSearch } from \"@agentero/design-system\";"
6949
+ },
6782
6950
  "components-inputsearch": {
6783
6951
  "id": "components-inputsearch",
6784
6952
  "name": "InputSearch",
@@ -6941,6 +7109,154 @@
6941
7109
  ],
6942
7110
  "import": "import { Field, FieldText, InputSearch, Label } from \"@agentero/design-system\";"
6943
7111
  },
7112
+ "components-inputtel": {
7113
+ "id": "components-inputtel",
7114
+ "name": "InputTel",
7115
+ "path": "./src/input-tel/input-tel.stories.tsx",
7116
+ "jsDocTags": {
7117
+ "summary": [
7118
+ "US phone input with a +1 prefix that masks as you type"
7119
+ ],
7120
+ "dataAttribute": [
7121
+ "{string} data-slot - Always set to \"input-tel\" on the frame"
7122
+ ],
7123
+ "example": [
7124
+ "<FieldText>\n <Label>Phone number</Label>\n <InputTel name=\"phone\" autoComplete=\"tel-national\" placeholder=\"(555) 000-0000\" />\n</FieldText>"
7125
+ ]
7126
+ },
7127
+ "description": "InputTel is a US phone number field that masks as you type: the digits are\nlaid out as `(555) 123-4567`, anything else is dropped and the caret stays\nnext to the digit just edited. It is an `Input` of `type=\"tel\"` inside an\n`InputGroup` frame with a `+1` prefix; `onChange` receives the native event\nwith the masked text already in `event.target.value`.",
7128
+ "summary": "US phone input with a +1 prefix that masks as you type",
7129
+ "reactComponentMeta": {
7130
+ "displayName": "InputTel",
7131
+ "exportName": "InputTel",
7132
+ "filePath": "/home/runner/work/design-system/design-system/src/input-tel/input-tel.tsx",
7133
+ "description": "InputTel is a US phone number field that masks as you type: the digits the\nuser enters are laid out as `(555) 123-4567` while typing, pasting or\ndeleting, anything that is not a digit is dropped, and the caret stays next\nto the digit just edited instead of jumping to the end. A leading `+1`\ncountry code, as pasted or filled in by the browser's autofill, is dropped\ntoo; past that, the eleventh digit and beyond are ignored. `onChange`\nreceives the native change event with the masked text already in\n`event.target.value`, so a controlled field, an uncontrolled one and one\nregistered with react-hook-form all store `(555) 123-4567`, never the bare\ndigits.\n\nIt is an [Input](?path=/docs/components-input--docs) of `type=\"tel\"` inside\nan [InputGroup](?path=/docs/components-inputgroup--docs) frame that always\nshows a `+1` prefix in front of the number. The prefix is part of the frame,\nnot of the value. `className` styles the frame. Every other prop goes to the\n`<input>`: the `ref`, `id`, `name`, `value`, `onChange`, `aria-*` and every\nother standard attribute, such as `placeholder`, `autoComplete` or\n`required`. Only `type` is not accepted. Inside a `FieldText` the input\ntakes its label, messages and states from the field as a plain `Input` does.\nThe invalid state comes from `aria-invalid`, and the frame paints the\ndestructive border.\n\nDo not use it for international numbers or extensions; the mask only knows\nthe ten-digit US format. For free-form text that happens to hold a number,\nuse `Input`.",
7134
+ "jsDocTags": {
7135
+ "summary": [
7136
+ "US phone input with a +1 prefix that masks as you type"
7137
+ ],
7138
+ "dataAttribute": [
7139
+ "{string} data-slot - Always set to \"input-tel\" on the frame"
7140
+ ],
7141
+ "example": [
7142
+ "<FieldText>\n <Label>Phone number</Label>\n <InputTel name=\"phone\" autoComplete=\"tel-national\" placeholder=\"(555) 000-0000\" />\n</FieldText>"
7143
+ ]
7144
+ },
7145
+ "props": {
7146
+ "size": {
7147
+ "name": "size",
7148
+ "required": false,
7149
+ "type": {
7150
+ "name": "enum",
7151
+ "raw": "\"sm\" | \"md\" | \"lg\"",
7152
+ "value": [
7153
+ {
7154
+ "value": "\"sm\""
7155
+ },
7156
+ {
7157
+ "value": "\"md\""
7158
+ },
7159
+ {
7160
+ "value": "\"lg\""
7161
+ }
7162
+ ]
7163
+ },
7164
+ "description": "Control height. Defaults to `'md'`.\n- `sm` (32px) — dense layouts where vertical space is tight.\n- `md` (40px) — standard form density.\n- `lg` (48px) — low-density forms and larger touch targets. Also raises\n the text to `base` and the corner radius to `lg`.",
7165
+ "defaultValue": null,
7166
+ "parent": {
7167
+ "name": "InputProps",
7168
+ "fileName": "src/input/input.tsx"
7169
+ },
7170
+ "declarations": [
7171
+ {
7172
+ "name": "TypeLiteral",
7173
+ "fileName": "src/input/input.tsx"
7174
+ }
7175
+ ]
7176
+ }
7177
+ }
7178
+ },
7179
+ "stories": [
7180
+ {
7181
+ "id": "components-inputtel--default",
7182
+ "name": "Default",
7183
+ "snippet": "const Default = () => <InputTel\n aria-label=\"Phone number\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)} />;",
7184
+ "description": "An uncontrolled phone field with its `+1` prefix. Typing the ten digits lays\nthem out as `(555) 123-4567`, and every `onChange` call carries the masked\ntext in `event.target.value`; the prefix never enters the value.",
7185
+ "summary": "Phone field with a +1 prefix masking the digits as they are typed"
7186
+ },
7187
+ {
7188
+ "id": "components-inputtel--controlled",
7189
+ "name": "Controlled",
7190
+ "snippet": "const Controlled = () => {\n const [phone, setPhone] = useState('');\n\n return (\n <InputTel\n aria-label=\"Phone number\"\n value={phone}\n onChange={event => {\n setPhone(event.target.value);\n args.onChange?.(event);\n }} />\n );\n};",
7191
+ "description": "A controlled phone field. The state that owns the value stores what\n`event.target.value` reports, which is already masked, and the input shows\nit back unchanged. A partial number carries the separator that comes before\nthe next digit, so six digits read `(555) 123-`.",
7192
+ "summary": "Controlled phone field storing the masked value"
7193
+ },
7194
+ {
7195
+ "id": "components-inputtel--caret-in-the-middle",
7196
+ "name": "Caret In The Middle",
7197
+ "snippet": "const CaretInTheMiddle = () => <InputTel\n aria-label=\"Phone number\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)} />;",
7198
+ "description": "Editing in the middle of a full number: a digit typed after `(5` shifts the\nrest right, the eleventh digit falls off the end and the caret stays after\nthe digit just typed instead of jumping to the end.",
7199
+ "summary": "Caret staying put when a digit is inserted mid-number"
7200
+ },
7201
+ {
7202
+ "id": "components-inputtel--backspace-over-mask-characters",
7203
+ "name": "Backspace Over Mask Characters",
7204
+ "snippet": "const BackspaceOverMaskCharacters = () => <InputTel\n aria-label=\"Phone number\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)} />;",
7205
+ "description": "Backspace over the mask's own characters: after `555` the field shows\n`(555) `, and deleting backwards trims the separators along with the digit\nunder the caret instead of leaving a dangling `) `.",
7206
+ "summary": "Backspace trimming the separators the mask added"
7207
+ },
7208
+ {
7209
+ "id": "components-inputtel--paste",
7210
+ "name": "Paste",
7211
+ "snippet": "const Paste = () => <InputTel\n aria-label=\"Phone number\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)} />;",
7212
+ "description": "A pasted number in any format is reduced to its digits and masked, so\n`555-123-4567` from a clipboard ends up as `(555) 123-4567`.",
7213
+ "summary": "Pasted number reduced to its digits and masked"
7214
+ },
7215
+ {
7216
+ "id": "components-inputtel--paste-with-country-code",
7217
+ "name": "Paste With Country Code",
7218
+ "snippet": "const PasteWithCountryCode = () => <InputTel\n aria-label=\"Phone number\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)} />;",
7219
+ "description": "A number that carries the `+1` country code, as copied from a signature or\nfilled in by the browser's autofill, loses the code and keeps its ten\nnational digits: `+1 (555) 123-4567` ends up as `(555) 123-4567`.",
7220
+ "summary": "Pasted number with a +1 country code"
7221
+ },
7222
+ {
7223
+ "id": "components-inputtel--inside-field-text",
7224
+ "name": "Inside Field Text",
7225
+ "snippet": "const InsideFieldText = () => (\n <FieldText>\n <Label>Phone number</Label>\n <InputTel />\n <Field.Description>We only call about your account.</Field.Description>\n </FieldText>\n);",
7226
+ "description": "Inside a `FieldText` the input takes its label, description and states from\nthe field, and the masked value still reaches a handler the field provides.",
7227
+ "summary": "Label and description reaching the input through the field"
7228
+ },
7229
+ {
7230
+ "id": "components-inputtel--with-react-hook-form",
7231
+ "name": "With React Hook Form",
7232
+ "snippet": "const WithReactHookForm = () => {\n const { register, handleSubmit } = useForm<ContactValues>({\n defaultValues: { phone: '' }\n });\n\n return (\n <form\n aria-label=\"Contact\"\n noValidate\n onSubmit={handleSubmit(values => onSubmit(values))}\n style={{ display: 'grid', gap: '1rem' }}>\n <InputTel aria-label=\"Phone number\" {...register('phone')} />\n <Button type=\"submit\">Save</Button>\n </form>\n );\n};",
7233
+ "description": "Registered with react-hook-form, the form receives the masked value: the\n`register` handler reads `event.target.value` like any other change.",
7234
+ "summary": "Masked value reaching react-hook-form through register"
7235
+ },
7236
+ {
7237
+ "id": "components-inputtel--disabled",
7238
+ "name": "Disabled",
7239
+ "snippet": "const Disabled = () => <InputTel\n aria-label=\"Phone number\"\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n disabled\n defaultValue=\"(555) 123-4567\" />;",
7240
+ "description": "A disabled phone field keeps its masked value and takes no input.",
7241
+ "summary": "Disabled phone field"
7242
+ },
7243
+ {
7244
+ "id": "components-inputtel--invalid",
7245
+ "name": "Invalid",
7246
+ "snippet": "const Invalid = () => (\n <FieldText invalid>\n <Label>Phone number</Label>\n <InputTel defaultValue=\"(555) 12\" />\n <Field.Error errors={[{ message: 'Enter a 10-digit phone number.' }]} />\n </FieldText>\n);",
7247
+ "description": "`invalid` on the surrounding `FieldText` sets `aria-invalid` on the input,\nwhich paints the frame's destructive border, and `Field.Error` is announced\nwith the input.",
7248
+ "summary": "Invalid field driving the frame's destructive border"
7249
+ },
7250
+ {
7251
+ "id": "components-inputtel--all-sizes",
7252
+ "name": "All Sizes",
7253
+ "snippet": "const AllSizes = () => <div style={{ display: 'grid', gap: '1rem' }}>\n <InputTel\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n size=\"sm\"\n aria-label=\"Small\"\n defaultValue=\"(555) 123-4567\" />\n <InputTel\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n size=\"md\"\n aria-label=\"Medium\"\n defaultValue=\"(555) 123-4567\" />\n <InputTel\n onChange={fn((event: ChangeEvent<HTMLInputElement>) => event.target.value)}\n size=\"lg\"\n aria-label=\"Large\"\n defaultValue=\"(555) 123-4567\" />\n</div>;",
7254
+ "description": "The three sizes match Input's: the frame is 32, 40 or 48px tall.",
7255
+ "summary": "The three sizes side by side"
7256
+ }
7257
+ ],
7258
+ "import": "import { Button, Field, FieldText, InputContext, InputTel, Label } from \"@agentero/design-system\";"
7259
+ },
6944
7260
  "components-input": {
6945
7261
  "id": "components-input",
6946
7262
  "name": "Input",
@@ -9907,6 +10223,6 @@
9907
10223
  },
9908
10224
  "meta": {
9909
10225
  "docgen": "react-component-meta",
9910
- "durationMs": 3150
10226
+ "durationMs": 3866
9911
10227
  }
9912
10228
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentero/design-system",
3
- "version": "0.34.2",
3
+ "version": "0.35.1",
4
4
  "description": "A React component library built with Tailwind CSS v4 and Radix UI primitives",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -105,6 +105,10 @@
105
105
  "types": "./src/input-search/index.d.ts",
106
106
  "import": "./src/input-search/index.js"
107
107
  },
108
+ "./input-tel": {
109
+ "types": "./src/input-tel/index.d.ts",
110
+ "import": "./src/input-tel/index.js"
111
+ },
108
112
  "./label": {
109
113
  "types": "./src/label/index.d.ts",
110
114
  "import": "./src/label/index.js"
@@ -212,6 +216,7 @@
212
216
  "cmdk": "^1.1.1",
213
217
  "sonner": "^2.0.7",
214
218
  "tailwind-merge": "^3.5.0",
215
- "tailwind-variants": "^3.2.2"
219
+ "tailwind-variants": "^3.2.2",
220
+ "use-debounce": "^10.1.1"
216
221
  }
217
222
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Milliseconds DebouncedInputSearch waits after the last keystroke before it
3
+ * calls `onChange`.
4
+ *
5
+ * @summary Debounce delay of DebouncedInputSearch, in milliseconds
6
+ */
7
+ export declare const DEBOUNCE_TIME = 300;
@@ -0,0 +1,4 @@
1
+ //#region src/input-search/constants.ts
2
+ var e = 300;
3
+ //#endregion
4
+ export { e as DEBOUNCE_TIME };
@@ -0,0 +1,56 @@
1
+ import { ChangeEventHandler } from 'react';
2
+ import { InputSearchProps } from './input-search';
3
+ export type DebouncedInputSearchProps = Omit<InputSearchProps, 'value' | 'defaultValue' | 'onChange'> & {
4
+ /**
5
+ * The query the page is filtering by. The field shows it, and follows it
6
+ * when it changes from outside (a reset button, a saved filter, navigation),
7
+ * unless the user is in the middle of typing: then what they typed stays,
8
+ * and `onChange` reports it once they pause.
9
+ */
10
+ value: string;
11
+ /**
12
+ * Fires once the user has stopped typing for `DEBOUNCE_TIME` milliseconds,
13
+ * with the change event of the last keystroke; clearing the field goes
14
+ * through the same delay. A pending call fires at once when the user
15
+ * presses Enter, and is dropped when the field unmounts, so a page that is
16
+ * navigating away is not sent back. Read the text from `event.target.value`, which is
17
+ * what the field shows at that moment. `event.currentTarget` is no longer
18
+ * set by then, since the event is delivered after the browser's dispatch.
19
+ */
20
+ onChange: ChangeEventHandler<HTMLInputElement>;
21
+ };
22
+ /**
23
+ * DebouncedInputSearch is an [InputSearch](?path=/docs/components-inputsearch--docs)
24
+ * for filters that are expensive to run: a table query, a request to the
25
+ * server. Keystrokes show up in the field at once, but `onChange` fires only
26
+ * once the user pauses for `DEBOUNCE_TIME` (300ms), with the last change
27
+ * event, so the page runs one search per pause instead of one per key. It is
28
+ * always controlled: `value` and `onChange` are required, the page owns the
29
+ * query, and the field follows a `value` changed from outside while the user
30
+ * is not typing.
31
+ *
32
+ * Every other prop goes to the InputSearch and from there to the `<input>`:
33
+ * the `ref`, `size`, `isLoading`, `placeholder`, `aria-*` and every other
34
+ * standard attribute. Only `type` is not accepted. `className` styles the
35
+ * frame.
36
+ *
37
+ * Do not use it where every keystroke matters, such as filtering a short list
38
+ * already in memory; that is a plain
39
+ * [InputSearch](?path=/docs/components-inputsearch--docs). To pick a value from
40
+ * the results, use [Combobox](?path=/docs/components-combobox--docs).
41
+ *
42
+ * @summary Search input that reports the query once the user pauses typing
43
+ * @dataAttribute {string} data-slot - Always set to "input-search" on the frame
44
+ *
45
+ * @example
46
+ * <DebouncedInputSearch
47
+ * aria-label="Search agencies"
48
+ * value={filters.query}
49
+ * onChange={event => setFilters({ ...filters, query: event.target.value })}
50
+ * isLoading={isFetching}
51
+ * />
52
+ */
53
+ export declare const DebouncedInputSearch: {
54
+ ({ value, onChange, onKeyDown, ...props }: DebouncedInputSearchProps): import("react/jsx-runtime").JSX.Element;
55
+ displayName: string;
56
+ };
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { chain as e } from "../../lib/merge-props.js";
3
+ import "./constants.js";
4
+ import { InputSearch as t } from "./input-search.js";
5
+ import { useState as n } from "react";
6
+ import { jsx as r } from "react/jsx-runtime";
7
+ import { useDebouncedCallback as i } from "use-debounce";
8
+ //#region src/input-search/debounced-input-search.tsx
9
+ var a = ({ value: a, onChange: o, onKeyDown: s, ...c }) => {
10
+ let [l, u] = n(a), [d, f] = n(a), p = i(o, 300);
11
+ a !== d && (f(a), p.isPending() || u(a));
12
+ let m = (e) => {
13
+ u(e.target.value), p(e);
14
+ }, h = (e) => {
15
+ let t = e.nativeEvent.isComposing || e.keyCode === 229;
16
+ e.key === "Enter" && !t && p.flush();
17
+ };
18
+ return /* @__PURE__ */ r(t, {
19
+ ...c,
20
+ value: l,
21
+ onChange: m,
22
+ onKeyDown: e(h, s)
23
+ });
24
+ };
25
+ a.displayName = "DebouncedInputSearch";
26
+ //#endregion
27
+ export { a as DebouncedInputSearch };
@@ -1,2 +1,5 @@
1
+ export { DEBOUNCE_TIME } from './constants';
2
+ export { DebouncedInputSearch } from './debounced-input-search';
3
+ export type { DebouncedInputSearchProps } from './debounced-input-search';
1
4
  export { InputSearch, inputSearchRecipe } from './input-search';
2
5
  export type { InputSearchProps } from './input-search';
@@ -1,2 +1,4 @@
1
- import { InputSearch as e, inputSearchRecipe as t } from "./input-search.js";
2
- export { e as InputSearch, t as inputSearchRecipe };
1
+ import { DEBOUNCE_TIME as e } from "./constants.js";
2
+ import { InputSearch as t, inputSearchRecipe as n } from "./input-search.js";
3
+ import { DebouncedInputSearch as r } from "./debounced-input-search.js";
4
+ export { e as DEBOUNCE_TIME, r as DebouncedInputSearch, t as InputSearch, n as inputSearchRecipe };
@@ -0,0 +1,2 @@
1
+ export { InputTel } from './input-tel';
2
+ export type { InputTelProps } from './input-tel';
@@ -0,0 +1,2 @@
1
+ import { InputTel as e } from "./input-tel.js";
2
+ export { e as InputTel };
@@ -0,0 +1,42 @@
1
+ import { InputProps } from '../input';
2
+ export type InputTelProps = Omit<InputProps, 'type'>;
3
+ /**
4
+ * InputTel is a US phone number field that masks as you type: the digits the
5
+ * user enters are laid out as `(555) 123-4567` while typing, pasting or
6
+ * deleting, anything that is not a digit is dropped, and the caret stays next
7
+ * to the digit just edited instead of jumping to the end. A leading `+1`
8
+ * country code, as pasted or filled in by the browser's autofill, is dropped
9
+ * too; past that, the eleventh digit and beyond are ignored. `onChange`
10
+ * receives the native change event with the masked text already in
11
+ * `event.target.value`, so a controlled field, an uncontrolled one and one
12
+ * registered with react-hook-form all store `(555) 123-4567`, never the bare
13
+ * digits.
14
+ *
15
+ * It is an [Input](?path=/docs/components-input--docs) of `type="tel"` inside
16
+ * an [InputGroup](?path=/docs/components-inputgroup--docs) frame that always
17
+ * shows a `+1` prefix in front of the number. The prefix is part of the frame,
18
+ * not of the value. `className` styles the frame. Every other prop goes to the
19
+ * `<input>`: the `ref`, `id`, `name`, `value`, `onChange`, `aria-*` and every
20
+ * other standard attribute, such as `placeholder`, `autoComplete` or
21
+ * `required`. Only `type` is not accepted. Inside a `FieldText` the input
22
+ * takes its label, messages and states from the field as a plain `Input` does.
23
+ * The invalid state comes from `aria-invalid`, and the frame paints the
24
+ * destructive border.
25
+ *
26
+ * Do not use it for international numbers or extensions; the mask only knows
27
+ * the ten-digit US format. For free-form text that happens to hold a number,
28
+ * use `Input`.
29
+ *
30
+ * @summary US phone input with a +1 prefix that masks as you type
31
+ * @dataAttribute {string} data-slot - Always set to "input-tel" on the frame
32
+ *
33
+ * @example
34
+ * <FieldText>
35
+ * <Label>Phone number</Label>
36
+ * <InputTel name="phone" autoComplete="tel-national" placeholder="(555) 000-0000" />
37
+ * </FieldText>
38
+ */
39
+ export declare const InputTel: {
40
+ (props: InputTelProps): import("react/jsx-runtime").JSX.Element;
41
+ displayName: string;
42
+ };
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { mergeRefs as e } from "../../lib/merge-props.js";
3
+ import { InputContext as t, useInputContext as n } from "../input/context.js";
4
+ import { Input as r } from "../input/input.js";
5
+ import { InputGroup as i } from "../input-group/index.js";
6
+ import { US_PHONE_MASK as a, applyMask as o, getCursorPosition as s, getDeletionType as c, toNationalDigits as l } from "./mask.js";
7
+ import { useMemo as u, useRef as d } from "react";
8
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
9
+ //#region src/input-tel/input-tel.tsx
10
+ var m = (m) => {
11
+ let { className: h, onChange: g, ref: _, ...v } = n(m), y = d(null), b = u(() => e(y, _), [_]), x = (e) => {
12
+ let t = c(e.nativeEvent.inputType), { value: n, selectionStart: r } = e.target, i = o({
13
+ value: l(n),
14
+ mask: a,
15
+ maskSymbol: ".",
16
+ trimNonMaskCharsLeftover: t === "backward"
17
+ }), u = s({
18
+ cursorPositionAfterInput: r ?? 0,
19
+ phoneAfterInput: n,
20
+ phoneAfterFormatted: i,
21
+ deletion: t
22
+ });
23
+ e.target.value = i;
24
+ let d = () => {
25
+ let e = y.current;
26
+ e && e === document.activeElement && e.setSelectionRange(u, u);
27
+ };
28
+ g ? (g(e), Promise.resolve().then(d)) : d();
29
+ };
30
+ return /* @__PURE__ */ p(i.Root, {
31
+ "data-slot": "input-tel",
32
+ className: h,
33
+ children: [/* @__PURE__ */ f(i.Text, { children: "+1" }), /* @__PURE__ */ f(t, {
34
+ value: null,
35
+ children: /* @__PURE__ */ f(r, {
36
+ ...v,
37
+ type: "tel",
38
+ ref: b,
39
+ onChange: x
40
+ })
41
+ })]
42
+ });
43
+ };
44
+ m.displayName = "InputTel";
45
+ //#endregion
46
+ export { m as InputTel };
@@ -0,0 +1,51 @@
1
+ /** The US phone mask InputTel applies: three groups of 3, 3 and 4 digits. */
2
+ export declare const US_PHONE_MASK = "(...) ...-....";
3
+ /** The character that marks a digit slot in a mask. */
4
+ export declare const MASK_SYMBOL = ".";
5
+ /**
6
+ * Reduces a phone number in any format to its national digits: drops
7
+ * everything that is not a digit and, past ten digits, the leading `1` of the
8
+ * `+1` country code. No US area code starts with `1`, so a national number is
9
+ * never cut.
10
+ */
11
+ export declare const toNationalDigits: (value: string) => string;
12
+ type ApplyMaskArgs = {
13
+ value: string;
14
+ mask: string;
15
+ maskSymbol: string;
16
+ /**
17
+ * @description Removes all non-maskSymbol chars from the result's ending if the value is shorter than the mask
18
+ * @example
19
+ * value: "1234"
20
+ * mask: "(....) ...."
21
+ * if true -> "(1234"
22
+ * if false -> "(1234) "
23
+ */
24
+ trimNonMaskCharsLeftover?: boolean;
25
+ };
26
+ /**
27
+ * Places each character of `value` in the next `maskSymbol` slot of `mask`,
28
+ * keeping the mask's own characters in between. A value longer than the mask
29
+ * is cut; a shorter one stops at its last character, followed by the
30
+ * separators up to the next slot unless `trimNonMaskCharsLeftover` is set.
31
+ */
32
+ export declare const applyMask: ({ value, mask, maskSymbol, trimNonMaskCharsLeftover }: ApplyMaskArgs) => string;
33
+ export declare const isNumeric: (str?: string) => boolean;
34
+ type GetCursorPositionProps = {
35
+ phoneAfterInput: string;
36
+ phoneAfterFormatted: string;
37
+ cursorPositionAfterInput: number;
38
+ deletion?: 'forward' | 'backward' | undefined;
39
+ };
40
+ /**
41
+ * Finds where the caret should sit in the formatted phone after an edit, so
42
+ * that it stays after the digit the user just typed or removed instead of
43
+ * jumping to the end when the mask characters move around it.
44
+ */
45
+ export declare const getCursorPosition: ({ phoneAfterInput, phoneAfterFormatted, cursorPositionAfterInput, deletion }: GetCursorPositionProps) => number;
46
+ /**
47
+ * Reads the `inputType` of a native `input` event and tells whether the edit
48
+ * removed characters, and in which direction. `undefined` for an insertion.
49
+ */
50
+ export declare const getDeletionType: (inputType?: string) => "forward" | "backward" | undefined;
51
+ export {};