@devalok/shilp-sutra 0.52.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +7 -1
- package/BREAKING.json +29 -0
- package/MIGRATION.md +61 -0
- package/dist/_chunks/motion-provider.js +7 -6
- package/dist/_chunks/motion-provider.js.map +1 -1
- package/dist/_chunks/success.js +53 -53
- package/dist/ai/command-bar.js +158 -158
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/ai/conversation.js +5 -5
- package/dist/composed/avatar-group.d.ts +11 -9
- package/dist/composed/avatar-group.d.ts.map +1 -1
- package/dist/composed/avatar-group.js +108 -140
- package/dist/composed/avatar-group.js.map +1 -1
- package/dist/composed/bulk-action-bar.d.ts +19 -11
- package/dist/composed/bulk-action-bar.d.ts.map +1 -1
- package/dist/composed/bulk-action-bar.js +81 -110
- package/dist/composed/bulk-action-bar.js.map +1 -1
- package/dist/composed/command-palette.js +17 -17
- package/dist/composed/command-palette.js.map +1 -1
- package/dist/composed/error-boundary.d.ts +15 -3
- package/dist/composed/error-boundary.d.ts.map +1 -1
- package/dist/composed/error-boundary.js +43 -26
- package/dist/composed/error-boundary.js.map +1 -1
- package/dist/composed/loading-skeleton.d.ts +2 -0
- package/dist/composed/loading-skeleton.d.ts.map +1 -1
- package/dist/composed/loading-skeleton.js +79 -56
- package/dist/composed/loading-skeleton.js.map +1 -1
- package/dist/composed/master-detail.d.ts +21 -8
- package/dist/composed/master-detail.d.ts.map +1 -1
- package/dist/composed/master-detail.js +91 -77
- package/dist/composed/master-detail.js.map +1 -1
- package/dist/composed/page-skeletons.d.ts +6 -0
- package/dist/composed/page-skeletons.d.ts.map +1 -1
- package/dist/composed/page-skeletons.js +93 -71
- package/dist/composed/page-skeletons.js.map +1 -1
- package/dist/composed/priority-indicator.d.ts +15 -6
- package/dist/composed/priority-indicator.d.ts.map +1 -1
- package/dist/composed/priority-indicator.js +37 -88
- package/dist/composed/priority-indicator.js.map +1 -1
- package/dist/composed/schedule-view.d.ts +17 -2
- package/dist/composed/schedule-view.d.ts.map +1 -1
- package/dist/composed/schedule-view.js +163 -64
- package/dist/composed/schedule-view.js.map +1 -1
- package/dist/motion/motion-provider.d.ts.map +1 -1
- package/dist/shell/bottom-navbar.d.ts +32 -6
- package/dist/shell/bottom-navbar.d.ts.map +1 -1
- package/dist/shell/bottom-navbar.js +156 -129
- package/dist/shell/bottom-navbar.js.map +1 -1
- package/dist/shell/index.d.ts +5 -3
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +2 -3
- package/dist/shell/notification-preferences.d.ts.map +1 -1
- package/dist/shell/notification-preferences.js +2 -0
- package/dist/shell/notification-preferences.js.map +1 -1
- package/dist/tokens/primitives.css +6 -2
- package/dist/tokens/utilities.css +6 -0
- package/dist/ui/autocomplete.d.ts +34 -32
- package/dist/ui/autocomplete.d.ts.map +1 -1
- package/dist/ui/autocomplete.js +115 -110
- package/dist/ui/autocomplete.js.map +1 -1
- package/dist/ui/combobox.d.ts +1 -1
- package/dist/ui/combobox.d.ts.map +1 -1
- package/dist/ui/combobox.js +133 -128
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/file-upload.d.ts.map +1 -1
- package/dist/ui/file-upload.js +124 -138
- package/dist/ui/file-upload.js.map +1 -1
- package/dist/ui/form.d.ts +7 -0
- package/dist/ui/form.d.ts.map +1 -1
- package/dist/ui/form.js +4 -2
- package/dist/ui/form.js.map +1 -1
- package/dist/ui/icon-button.d.ts +2 -1
- package/dist/ui/icon-button.d.ts.map +1 -1
- package/dist/ui/icon-button.js +1 -1
- package/dist/ui/icon-button.js.map +1 -1
- package/dist/ui/index.js +30 -30
- package/dist/ui/label.d.ts.map +1 -1
- package/dist/ui/label.js +7 -6
- package/dist/ui/label.js.map +1 -1
- package/dist/ui/search-input.js +4 -4
- package/dist/ui/sidebar.js +7 -7
- package/dist/ui/spinner.d.ts.map +1 -1
- package/dist/ui/spinner.js +50 -44
- package/dist/ui/spinner.js.map +1 -1
- package/dist/ui/switch.d.ts.map +1 -1
- package/dist/ui/switch.js +24 -24
- package/dist/ui/switch.js.map +1 -1
- package/dist/ui/table.js +1 -1
- package/dist/ui/table.js.map +1 -1
- package/docs/components/composed/bulk-action-bar.md +9 -3
- package/docs/components/composed/error-boundary.md +18 -2
- package/docs/components/composed/master-detail.md +12 -2
- package/docs/components/composed/page-skeletons.md +2 -2
- package/docs/components/composed/priority-indicator.md +22 -11
- package/docs/components/composed/rich-chat-input.md +18 -6
- package/docs/components/composed/schedule-view.md +20 -5
- package/docs/components/composed/simple-tooltip.md +1 -1
- package/docs/components/shell/app-command-palette.md +2 -1
- package/docs/components/shell/bottom-navbar.md +28 -9
- package/docs/components/shell/command-registry.md +2 -2
- package/docs/components/shell/link-context.md +2 -2
- package/docs/components/shell/top-bar.md +1 -1
- package/docs/components/ui/autocomplete.md +26 -10
- package/docs/components/ui/search-input.md +2 -2
- package/docs/components/ui/slider.md +2 -2
- package/docs/components/ui/split-button.md +3 -3
- package/llms.txt +2 -2
- package/make-kit/Guidelines.md +1 -1
- package/make-kit/components/overview.md +2 -2
- package/make-kit/foundations/surfaces.md +1 -1
- package/mcp-manifest.json +581 -172
- package/package.json +1 -6
- package/skill/SKILL.md +1 -1
- package/skill/chat-skill/README.md +18 -0
- package/skill/chat-skill/SKILL.md +78 -0
- package/skill/chat-skill/chatgpt-instructions.md +26 -0
- package/skill/chat-skill/gemini-instructions.md +25 -0
- package/skill/references/components.md +2 -2
- package/dist/shell/sidebar.d.ts +0 -113
- package/dist/shell/sidebar.d.ts.map +0 -1
- package/dist/shell/sidebar.js +0 -293
- package/dist/shell/sidebar.js.map +0 -1
- package/docs/components/shell/sidebar.md +0 -103
|
@@ -1,59 +1,61 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { InputProps } from './input';
|
|
2
3
|
import * as React from 'react';
|
|
3
|
-
type AutocompleteOption = {
|
|
4
|
+
export type AutocompleteOption = {
|
|
4
5
|
label: string;
|
|
5
6
|
value: string;
|
|
6
7
|
};
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* A free-text input with a live-filtered dropdown, keyboard navigation, and
|
|
10
|
+
* combobox ARIA. The field itself is the DS `Input`, so it inherits size,
|
|
11
|
+
* error/state painting, read-only, hover, and FormField wiring.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* with suggestions; use Combobox for structured single or multi-select dropdowns.
|
|
15
|
-
*
|
|
16
|
-
* **`value`:** A full `AutocompleteOption` object (or null), not just the string value.
|
|
17
|
-
* The input's text is synced to `value.label` on mount.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* // City search autocomplete:
|
|
21
|
-
* <Autocomplete
|
|
22
|
-
* options={[{ value: 'mumbai', label: 'Mumbai' }, { value: 'delhi', label: 'Delhi' }]}
|
|
23
|
-
* value={selectedCity}
|
|
24
|
-
* onValueChange={(opt) => setSelectedCity(opt)}
|
|
25
|
-
* placeholder="Search cities..."
|
|
26
|
-
* />
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* // Employee name lookup with custom empty text:
|
|
30
|
-
* <Autocomplete
|
|
31
|
-
* options={employees.map(e => ({ value: e.id, label: e.fullName }))}
|
|
32
|
-
* onValueChange={(opt) => setAssignee(opt.value)}
|
|
33
|
-
* emptyText="No employees found"
|
|
34
|
-
* placeholder="Search employees..."
|
|
35
|
-
* />
|
|
36
|
-
* // These are just a few ways — feel free to combine props creatively!
|
|
13
|
+
* Free-text (no forced selection) — for enforced selection use `Combobox`.
|
|
14
|
+
* `value`/`defaultValue` are a full `AutocompleteOption` (or null).
|
|
37
15
|
*/
|
|
38
|
-
type AutocompleteProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
16
|
+
export type AutocompleteProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> & {
|
|
39
17
|
options: AutocompleteOption[];
|
|
18
|
+
/** Controlled selection. */
|
|
40
19
|
value?: AutocompleteOption | null;
|
|
20
|
+
/** Initial selection for uncontrolled mode. */
|
|
21
|
+
defaultValue?: AutocompleteOption | null;
|
|
41
22
|
onValueChange?: (option: AutocompleteOption) => void;
|
|
42
23
|
placeholder?: string;
|
|
43
24
|
emptyText?: string;
|
|
44
25
|
disabled?: boolean;
|
|
26
|
+
/** Input size (forwarded to the composed `Input`). */
|
|
27
|
+
size?: InputProps['size'];
|
|
28
|
+
/** Field state (forwarded to `Input`); overrides FormField auto-consumption. */
|
|
29
|
+
state?: InputProps['state'];
|
|
30
|
+
/** Show a loading spinner + `loadingText` (async "type to search"). */
|
|
31
|
+
isLoading?: boolean;
|
|
32
|
+
loadingText?: string;
|
|
33
|
+
/** Custom option renderer. Receives the option and the current query. */
|
|
34
|
+
renderOption?: (option: AutocompleteOption, query: string) => React.ReactNode;
|
|
45
35
|
className?: string;
|
|
46
36
|
id?: string;
|
|
47
37
|
};
|
|
48
|
-
declare const Autocomplete: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
38
|
+
declare const Autocomplete: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> & {
|
|
49
39
|
options: AutocompleteOption[];
|
|
40
|
+
/** Controlled selection. */
|
|
50
41
|
value?: AutocompleteOption | null;
|
|
42
|
+
/** Initial selection for uncontrolled mode. */
|
|
43
|
+
defaultValue?: AutocompleteOption | null;
|
|
51
44
|
onValueChange?: (option: AutocompleteOption) => void;
|
|
52
45
|
placeholder?: string;
|
|
53
46
|
emptyText?: string;
|
|
54
47
|
disabled?: boolean;
|
|
48
|
+
/** Input size (forwarded to the composed `Input`). */
|
|
49
|
+
size?: InputProps["size"];
|
|
50
|
+
/** Field state (forwarded to `Input`); overrides FormField auto-consumption. */
|
|
51
|
+
state?: InputProps["state"];
|
|
52
|
+
/** Show a loading spinner + `loadingText` (async "type to search"). */
|
|
53
|
+
isLoading?: boolean;
|
|
54
|
+
loadingText?: string;
|
|
55
|
+
/** Custom option renderer. Receives the option and the current query. */
|
|
56
|
+
renderOption?: (option: AutocompleteOption, query: string) => React.ReactNode;
|
|
55
57
|
className?: string;
|
|
56
58
|
id?: string;
|
|
57
59
|
} & React.RefAttributes<HTMLInputElement>>;
|
|
58
|
-
export { Autocomplete
|
|
60
|
+
export { Autocomplete };
|
|
59
61
|
//# sourceMappingURL=autocomplete.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../../src/ui/autocomplete.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../../src/ui/autocomplete.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,SAAS,CAAA;AAKhD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,GAAG;IACvF,OAAO,EAAE,kBAAkB,EAAE,CAAA;IAC7B,4BAA4B;IAC5B,KAAK,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACjC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACxC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAA;IACpD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,sDAAsD;IACtD,IAAI,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAA;IACzB,gFAAgF;IAChF,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAC3B,uEAAuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yEAAyE;IACzE,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ,CAAA;AAeD,QAAA,MAAM,YAAY;aAnCP,kBAAkB,EAAE;IAC7B,4BAA4B;YACpB,kBAAkB,GAAG,IAAI;IACjC,+CAA+C;mBAChC,kBAAkB,GAAG,IAAI;oBACxB,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI;kBACtC,MAAM;gBACR,MAAM;eACP,OAAO;IAClB,sDAAsD;WAC/C,UAAU,CAAC,MAAM,CAAC;IACzB,gFAAgF;YACxE,UAAU,CAAC,OAAO,CAAC;IAC3B,uEAAuE;gBAC3D,OAAO;kBACL,MAAM;IACpB,yEAAyE;mBAC1D,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS;gBACjE,MAAM;SACb,MAAM;0CA2PZ,CAAA;AAGD,OAAO,EAAE,YAAY,EAAE,CAAA"}
|
package/dist/ui/autocomplete.js
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { autoUpdate as e, flip as t, offset as n, shift as r, size as i, useFloating as a } from "../_chunks/primitives.js";
|
|
3
|
-
import {
|
|
4
|
-
import { cn as
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
3
|
+
import { tweens as o } from "./lib/motion.js";
|
|
4
|
+
import { cn as s } from "./lib/utils.js";
|
|
5
|
+
import { Spinner as c } from "./spinner.js";
|
|
6
|
+
import { useMotion as l } from "../_chunks/motion-provider.js";
|
|
7
|
+
import { useFormField as u } from "./form.js";
|
|
8
|
+
import { Input as ee } from "./input.js";
|
|
9
|
+
import * as d from "react";
|
|
10
|
+
import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
11
|
+
import { createPortal as h } from "react-dom";
|
|
12
|
+
import { AnimatePresence as g, motion as _ } from "framer-motion";
|
|
10
13
|
//#region src/ui/autocomplete.tsx
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let D = u.useId(), O = w || D, k = `${O}-listbox`, A = `${O}-option`, [j, M] = u.useState(v?.label ?? ""), [N, P] = u.useState(!1), [F, I] = u.useState(-1), L = u.useRef(null), R = u.useRef(null), z = u.useRef(null), B = u.useRef(null), { refs: V, floatingStyles: H } = a({
|
|
26
|
-
open: N,
|
|
14
|
+
function v({ label: e, query: t }) {
|
|
15
|
+
let n = t ? e.toLowerCase().indexOf(t.toLowerCase()) : -1;
|
|
16
|
+
return n === -1 ? /* @__PURE__ */ p(f, { children: e }) : /* @__PURE__ */ m(f, { children: [
|
|
17
|
+
e.slice(0, n),
|
|
18
|
+
/* @__PURE__ */ p("span", {
|
|
19
|
+
className: "font-semibold text-accent-11",
|
|
20
|
+
children: e.slice(n, n + t.length)
|
|
21
|
+
}),
|
|
22
|
+
e.slice(n + t.length)
|
|
23
|
+
] });
|
|
24
|
+
}
|
|
25
|
+
var y = d.forwardRef(({ options: f, value: y, defaultValue: b = null, onValueChange: x, placeholder: S, emptyText: C = "No options", disabled: w, size: T, state: E, isLoading: D = !1, loadingText: O = "Loading…", renderOption: k, className: A, id: j, ...M }, N) => {
|
|
26
|
+
let te = d.useId(), P = j || te, F = `${P}-listbox`, I = `${P}-option`, L = y !== void 0, [R, z] = d.useState(b), B = L ? y : R, [V, H] = d.useState(B?.label ?? ""), [U, W] = d.useState(!1), [G, K] = d.useState(-1), q = d.useRef(null), J = d.useRef(null), Y = d.useRef(null), { reducedMotion: X } = l(), { refs: Z, floatingStyles: ne } = a({
|
|
27
|
+
open: U,
|
|
27
28
|
placement: "bottom-start",
|
|
28
29
|
whileElementsMounted: e,
|
|
29
30
|
middleware: [
|
|
@@ -37,110 +38,114 @@ var g = {
|
|
|
37
38
|
});
|
|
38
39
|
} })
|
|
39
40
|
]
|
|
40
|
-
}),
|
|
41
|
-
|
|
42
|
-
}, [
|
|
43
|
-
|
|
44
|
-
}, [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
41
|
+
}), re = d.useCallback((e) => {
|
|
42
|
+
J.current = e, Z.setReference(e);
|
|
43
|
+
}, [Z]), ie = d.useCallback((e) => {
|
|
44
|
+
q.current = e, typeof N == "function" ? N(e) : N && (N.current = e);
|
|
45
|
+
}, [N]), ae = d.useCallback((e) => {
|
|
46
|
+
Y.current = e, Z.setFloating(e);
|
|
47
|
+
}, [Z]);
|
|
48
|
+
d.useEffect(() => {
|
|
49
|
+
H(B?.label ?? "");
|
|
50
|
+
}, [B]);
|
|
51
|
+
let Q = d.useMemo(() => V ? f.filter((e) => e.label.toLowerCase().includes(V.toLowerCase())) : f, [f, V]), $ = d.useCallback((e) => {
|
|
52
|
+
H(e.label), W(!1), K(-1), L || z(e), x?.(e);
|
|
53
|
+
}, [L, x]), oe = d.useCallback((e) => {
|
|
54
|
+
if (!w) {
|
|
55
|
+
if (!U) {
|
|
56
|
+
(e.key === "ArrowDown" || e.key === "Enter") && W(!0);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
switch (e.key) {
|
|
60
|
+
case "ArrowDown":
|
|
61
|
+
e.preventDefault(), K((e) => Math.min(e + 1, Q.length - 1));
|
|
62
|
+
break;
|
|
63
|
+
case "ArrowUp":
|
|
64
|
+
e.preventDefault(), K((e) => Math.max(e - 1, 0));
|
|
65
|
+
break;
|
|
66
|
+
case "Home":
|
|
67
|
+
e.preventDefault(), K(0);
|
|
68
|
+
break;
|
|
69
|
+
case "End":
|
|
70
|
+
e.preventDefault(), K(Q.length - 1);
|
|
71
|
+
break;
|
|
72
|
+
case "Enter":
|
|
73
|
+
e.preventDefault(), G >= 0 && Q[G] && $(Q[G]);
|
|
74
|
+
break;
|
|
75
|
+
case "Escape":
|
|
76
|
+
W(!1), K(-1);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
74
79
|
}
|
|
75
80
|
}, [
|
|
76
|
-
|
|
81
|
+
w,
|
|
82
|
+
U,
|
|
83
|
+
Q,
|
|
77
84
|
G,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
ref: U,
|
|
85
|
+
$
|
|
86
|
+
]), se = u(), ce = !!(j || se.inputId), le = G >= 0 ? `${I}-${G}` : void 0;
|
|
87
|
+
return /* @__PURE__ */ m("div", {
|
|
88
|
+
ref: re,
|
|
89
|
+
className: s("relative", A),
|
|
90
|
+
...M,
|
|
91
|
+
children: [/* @__PURE__ */ p(ee, {
|
|
92
|
+
ref: ie,
|
|
87
93
|
type: "text",
|
|
88
94
|
role: "combobox",
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
size: T,
|
|
96
|
+
state: E,
|
|
97
|
+
id: j,
|
|
98
|
+
value: V,
|
|
99
|
+
placeholder: S,
|
|
100
|
+
disabled: w,
|
|
101
|
+
"aria-expanded": U,
|
|
91
102
|
"aria-autocomplete": "list",
|
|
92
|
-
"aria-controls":
|
|
93
|
-
"aria-activedescendant":
|
|
94
|
-
"aria-
|
|
95
|
-
"
|
|
96
|
-
"aria-required": Z || void 0,
|
|
97
|
-
"aria-label": w || J.inputId ? void 0 : b,
|
|
98
|
-
value: j,
|
|
99
|
-
placeholder: b,
|
|
100
|
-
disabled: S,
|
|
101
|
-
className: c("flex h-ds-md w-full rounded-control border border-surface-border-strong bg-surface-raised-hover px-ds-04 py-ds-03 font-sans text-body-md text-surface-fg placeholder:text-surface-fg-subtle", "outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-[var(--border-focus-offset)]", "transition-colors duration-fast-01", S && "opacity-action-disabled cursor-not-allowed"),
|
|
103
|
+
"aria-controls": U ? F : void 0,
|
|
104
|
+
"aria-activedescendant": le,
|
|
105
|
+
"aria-label": ce ? void 0 : S,
|
|
106
|
+
endSection: D ? /* @__PURE__ */ p(c, { size: "sm" }) : void 0,
|
|
102
107
|
onChange: (e) => {
|
|
103
|
-
|
|
108
|
+
H(e.target.value), W(!0), K(-1);
|
|
104
109
|
},
|
|
105
|
-
onFocus: () =>
|
|
110
|
+
onFocus: () => W(!0),
|
|
106
111
|
onBlur: (e) => {
|
|
107
112
|
let t = e.relatedTarget;
|
|
108
|
-
!
|
|
113
|
+
!J.current?.contains(t) && !Y.current?.contains(t) && W(!1);
|
|
109
114
|
},
|
|
110
|
-
onKeyDown:
|
|
111
|
-
}), typeof document < "u" &&
|
|
112
|
-
id:
|
|
113
|
-
ref:
|
|
115
|
+
onKeyDown: oe
|
|
116
|
+
}), typeof document < "u" && h(/* @__PURE__ */ p(g, { children: U && /* @__PURE__ */ p(_.ul, {
|
|
117
|
+
id: F,
|
|
118
|
+
ref: ae,
|
|
114
119
|
role: "listbox",
|
|
115
|
-
initial:
|
|
116
|
-
animate:
|
|
117
|
-
exit:
|
|
118
|
-
|
|
119
|
-
style:
|
|
120
|
-
className:
|
|
121
|
-
children:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
initial: !X && { opacity: 0 },
|
|
121
|
+
animate: { opacity: 1 },
|
|
122
|
+
exit: X ? { opacity: 1 } : { opacity: 0 },
|
|
123
|
+
transition: X ? { duration: 0 } : o.fade,
|
|
124
|
+
style: ne,
|
|
125
|
+
className: "z-popover overflow-auto rounded-overlay bg-surface-overlay shadow-raised-hover",
|
|
126
|
+
children: D ? /* @__PURE__ */ m("li", {
|
|
127
|
+
className: "flex items-center gap-ds-03 px-ds-04 py-ds-03 text-body-md text-surface-fg-muted",
|
|
128
|
+
children: [/* @__PURE__ */ p(c, { size: "sm" }), O]
|
|
129
|
+
}) : Q.length === 0 ? /* @__PURE__ */ p("li", {
|
|
125
130
|
className: "px-ds-04 py-ds-03 text-body-md text-surface-fg-muted",
|
|
126
|
-
children:
|
|
127
|
-
}) :
|
|
128
|
-
id: `${
|
|
131
|
+
children: C
|
|
132
|
+
}) : Q.map((e, t) => /* @__PURE__ */ p("li", {
|
|
133
|
+
id: `${I}-${t}`,
|
|
129
134
|
role: "option",
|
|
130
|
-
"aria-selected":
|
|
131
|
-
|
|
132
|
-
className:
|
|
135
|
+
"aria-selected": G === t,
|
|
136
|
+
title: e.label,
|
|
137
|
+
className: s("cursor-pointer truncate px-ds-04 py-ds-03 text-body-md text-surface-fg transition-colors duration-fast-01", G === t && "bg-accent-3", B?.value === e.value && "font-semibold"),
|
|
133
138
|
onMouseDown: (e) => e.preventDefault(),
|
|
134
|
-
onClick: () =>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
onClick: () => $(e),
|
|
140
|
+
onMouseEnter: () => K(t),
|
|
141
|
+
children: k ? k(e, V) : /* @__PURE__ */ p(v, {
|
|
142
|
+
label: e.label,
|
|
143
|
+
query: V
|
|
144
|
+
})
|
|
140
145
|
}, e.value))
|
|
141
146
|
}) }), document.body)]
|
|
142
147
|
});
|
|
143
148
|
});
|
|
144
|
-
|
|
149
|
+
y.displayName = "Autocomplete";
|
|
145
150
|
//#endregion
|
|
146
|
-
export {
|
|
151
|
+
export { y as Autocomplete };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autocomplete.js","names":[],"sources":["../../src/ui/autocomplete.tsx"],"sourcesContent":["'use client'\n\nimport { autoUpdate,flip, offset, shift, size, useFloating } from '@floating-ui/react-dom'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\n\nimport { useFormField } from './form'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\n\nconst listVariants = {\n hidden: {},\n visible: { transition: { staggerChildren: 0.03 } },\n}\n\nconst itemVariants = {\n hidden: { opacity: 0, y: 4 },\n visible: { opacity: 1, y: 0, transition: springs.snappy },\n}\n\ntype AutocompleteOption = {\n label: string\n value: string\n}\n\n/**\n * Props for Autocomplete — a free-text input with a live-filtered dropdown list, keyboard\n * navigation, and ARIA combobox semantics. Suitable for \"type to search\" fields where the\n * full list is known ahead of time (client-side filtering only).\n *\n * **Key distinction from Combobox:** Autocomplete allows free-text input (no forced selection),\n * while `<Combobox>` enforces selection from the list. Use Autocomplete for search-as-you-type\n * with suggestions; use Combobox for structured single or multi-select dropdowns.\n *\n * **`value`:** A full `AutocompleteOption` object (or null), not just the string value.\n * The input's text is synced to `value.label` on mount.\n *\n * @example\n * // City search autocomplete:\n * <Autocomplete\n * options={[{ value: 'mumbai', label: 'Mumbai' }, { value: 'delhi', label: 'Delhi' }]}\n * value={selectedCity}\n * onValueChange={(opt) => setSelectedCity(opt)}\n * placeholder=\"Search cities...\"\n * />\n *\n * @example\n * // Employee name lookup with custom empty text:\n * <Autocomplete\n * options={employees.map(e => ({ value: e.id, label: e.fullName }))}\n * onValueChange={(opt) => setAssignee(opt.value)}\n * emptyText=\"No employees found\"\n * placeholder=\"Search employees...\"\n * />\n * // These are just a few ways — feel free to combine props creatively!\n */\ntype AutocompleteProps = React.HTMLAttributes<HTMLDivElement> & {\n options: AutocompleteOption[]\n value?: AutocompleteOption | null\n onValueChange?: (option: AutocompleteOption) => void\n placeholder?: string\n emptyText?: string\n disabled?: boolean\n className?: string\n id?: string\n}\n\nconst Autocomplete = React.forwardRef<HTMLInputElement, AutocompleteProps>(\n (\n {\n options,\n value,\n onValueChange,\n placeholder,\n emptyText = 'No options',\n disabled,\n className,\n id: externalId,\n ...props\n },\n ref,\n ) => {\n const internalId = React.useId()\n const baseId = externalId || internalId\n const listboxId = `${baseId}-listbox`\n const optionIdPrefix = `${baseId}-option`\n\n const [query, setQuery] = React.useState(value?.label ?? '')\n const [isOpen, setIsOpen] = React.useState(false)\n const [highlightedIndex, setHighlightedIndex] = React.useState(-1)\n const internalRef = React.useRef<HTMLInputElement>(null)\n const listRef = React.useRef<HTMLUListElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const floatingRef = React.useRef<HTMLUListElement>(null)\n\n // Floating UI positioning\n const { refs, floatingStyles } = useFloating({\n open: isOpen,\n placement: 'bottom-start',\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(4),\n flip(),\n shift(),\n size({\n apply({ availableHeight, rects, elements }) {\n Object.assign(elements.floating.style, {\n maxHeight: `${Math.min(availableHeight, 240)}px`,\n width: `${rects.reference.width}px`,\n })\n },\n }),\n ],\n })\n\n // Compose external + internal ref + floating reference ref for input\n const composedRef = React.useCallback(\n (node: HTMLInputElement | null) => {\n (internalRef as React.MutableRefObject<HTMLInputElement | null>).current = node\n refs.setReference(node)\n if (typeof ref === 'function') ref(node)\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node\n },\n [ref, refs],\n )\n\n // Compose floating ref + listRef\n const composedFloatingRef = React.useCallback(\n (node: HTMLUListElement | null) => {\n (listRef as React.MutableRefObject<HTMLUListElement | null>).current = node\n ;(floatingRef as React.MutableRefObject<HTMLUListElement | null>).current = node\n refs.setFloating(node)\n },\n [refs],\n )\n\n // Sync query when value changes externally\n React.useEffect(() => {\n setQuery(value?.label ?? '')\n }, [value])\n\n // Cleanup blur timeout on unmount\n React.useEffect(() => {\n return () => {\n // Option selected — close and blur handled by relatedTarget check\n }\n }, [])\n\n const filtered = React.useMemo(\n () =>\n query\n ? options.filter((o) => o.label.toLowerCase().includes(query.toLowerCase()))\n : options,\n [options, query],\n )\n\n const handleSelect = React.useCallback(\n (option: AutocompleteOption) => {\n setQuery(option.label)\n setIsOpen(false)\n setHighlightedIndex(-1)\n onValueChange?.(option)\n },\n [onValueChange],\n )\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (!isOpen) {\n if (e.key === 'ArrowDown' || e.key === 'Enter') setIsOpen(true)\n return\n }\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault()\n setHighlightedIndex((i) => Math.min(i + 1, filtered.length - 1))\n break\n case 'ArrowUp':\n e.preventDefault()\n setHighlightedIndex((i) => Math.max(i - 1, 0))\n break\n case 'Home':\n e.preventDefault()\n setHighlightedIndex(0)\n break\n case 'End':\n e.preventDefault()\n setHighlightedIndex(filtered.length - 1)\n break\n case 'Enter':\n e.preventDefault()\n if (highlightedIndex >= 0 && filtered[highlightedIndex]) {\n handleSelect(filtered[highlightedIndex])\n }\n break\n case 'Escape':\n setIsOpen(false)\n setHighlightedIndex(-1)\n break\n }\n },\n [isOpen, filtered, highlightedIndex, handleSelect],\n )\n\n const fieldCtx = useFormField()\n const isError = fieldCtx.state === 'error'\n const ariaDescribedBy = fieldCtx.helperTextId\n const ariaRequired = fieldCtx.required\n\n const highlightedOptionId =\n highlightedIndex >= 0 ? `${optionIdPrefix}-${highlightedIndex}` : undefined\n\n return (\n <div ref={containerRef} className={cn('relative', className)} {...props}>\n <input\n ref={composedRef}\n type=\"text\"\n role=\"combobox\"\n // Explicit id wins; otherwise adopt FormField's inputId so <Label htmlFor> resolves.\n id={externalId ?? fieldCtx.inputId}\n aria-expanded={isOpen}\n aria-autocomplete=\"list\"\n aria-controls={isOpen ? listboxId : undefined}\n aria-activedescendant={highlightedOptionId}\n aria-invalid={isError || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n // Named by the associated <Label> (FormField) or an external label via id.\n // Only when there's no association mechanism, fall back to placeholder.\n aria-label={externalId || fieldCtx.inputId ? undefined : placeholder}\n value={query}\n placeholder={placeholder}\n disabled={disabled}\n className={cn(\n 'flex h-ds-md w-full rounded-control border border-surface-border-strong bg-surface-raised-hover px-ds-04 py-ds-03 font-sans text-body-md text-surface-fg placeholder:text-surface-fg-subtle',\n 'outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-[var(--border-focus-offset)]',\n 'transition-colors duration-fast-01',\n disabled && 'opacity-action-disabled cursor-not-allowed',\n )}\n onChange={(e) => {\n setQuery(e.target.value)\n setIsOpen(true)\n setHighlightedIndex(-1)\n }}\n onFocus={() => setIsOpen(true)}\n onBlur={(e) => {\n // Close only if focus moved outside both the container and the portal dropdown\n const target = e.relatedTarget as Node | null\n if (\n !containerRef.current?.contains(target) &&\n !floatingRef.current?.contains(target)\n ) {\n setIsOpen(false)\n }\n }}\n onKeyDown={handleKeyDown}\n />\n {typeof document !== 'undefined' &&\n createPortal(\n <AnimatePresence>\n {isOpen && (\n <motion.ul\n id={listboxId}\n ref={composedFloatingRef}\n role=\"listbox\"\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"hidden\"\n variants={listVariants}\n style={floatingStyles}\n className={cn(\n 'z-popover overflow-auto rounded-overlay bg-surface-overlay shadow-raised-hover',\n )}\n >\n {filtered.length === 0 ? (\n <motion.li\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={tweens.fade}\n className=\"px-ds-04 py-ds-03 text-body-md text-surface-fg-muted\"\n >\n {emptyText}\n </motion.li>\n ) : (\n filtered.map((option, index) => (\n <motion.li\n key={option.value}\n id={`${optionIdPrefix}-${index}`}\n role=\"option\"\n aria-selected={highlightedIndex === index}\n variants={itemVariants}\n className={cn(\n 'cursor-pointer px-ds-04 py-ds-03 text-body-md text-surface-fg transition-colors duration-fast-01',\n highlightedIndex === index && 'bg-accent-3',\n value?.value === option.value && 'font-semibold',\n )}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => handleSelect(option)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleSelect(option)\n }\n }}\n onMouseEnter={() => setHighlightedIndex(index)}\n >\n {option.label}\n </motion.li>\n ))\n )}\n </motion.ul>\n )}\n </AnimatePresence>,\n document.body,\n )}\n </div>\n )\n },\n)\nAutocomplete.displayName = 'Autocomplete'\n\nexport { Autocomplete, type AutocompleteOption,type AutocompleteProps }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"autocomplete.js","names":[],"sources":["../../src/ui/autocomplete.tsx"],"sourcesContent":["'use client'\n\nimport { autoUpdate, flip, offset, shift, size as sizeMiddleware, useFloating } from '@floating-ui/react-dom'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\n\nimport { useMotion } from '../motion/motion-provider'\nimport { useFormField } from './form'\nimport { Input, type InputProps } from './input'\nimport { tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Spinner } from './spinner'\n\nexport type AutocompleteOption = {\n label: string\n value: string\n}\n\n/**\n * A free-text input with a live-filtered dropdown, keyboard navigation, and\n * combobox ARIA. The field itself is the DS `Input`, so it inherits size,\n * error/state painting, read-only, hover, and FormField wiring.\n *\n * Free-text (no forced selection) — for enforced selection use `Combobox`.\n * `value`/`defaultValue` are a full `AutocompleteOption` (or null).\n */\nexport type AutocompleteProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> & {\n options: AutocompleteOption[]\n /** Controlled selection. */\n value?: AutocompleteOption | null\n /** Initial selection for uncontrolled mode. */\n defaultValue?: AutocompleteOption | null\n onValueChange?: (option: AutocompleteOption) => void\n placeholder?: string\n emptyText?: string\n disabled?: boolean\n /** Input size (forwarded to the composed `Input`). */\n size?: InputProps['size']\n /** Field state (forwarded to `Input`); overrides FormField auto-consumption. */\n state?: InputProps['state']\n /** Show a loading spinner + `loadingText` (async \"type to search\"). */\n isLoading?: boolean\n loadingText?: string\n /** Custom option renderer. Receives the option and the current query. */\n renderOption?: (option: AutocompleteOption, query: string) => React.ReactNode\n className?: string\n id?: string\n}\n\n/** Bold the matched substring inside an option label. */\nfunction HighlightMatch({ label, query }: { label: string; query: string }) {\n const idx = query ? label.toLowerCase().indexOf(query.toLowerCase()) : -1\n if (idx === -1) return <>{label}</>\n return (\n <>\n {label.slice(0, idx)}\n <span className=\"font-semibold text-accent-11\">{label.slice(idx, idx + query.length)}</span>\n {label.slice(idx + query.length)}\n </>\n )\n}\n\nconst Autocomplete = React.forwardRef<HTMLInputElement, AutocompleteProps>(\n (\n {\n options,\n value,\n defaultValue = null,\n onValueChange,\n placeholder,\n emptyText = 'No options',\n disabled,\n size,\n state,\n isLoading = false,\n loadingText = 'Loading…',\n renderOption,\n className,\n id: externalId,\n ...props\n },\n ref,\n ) => {\n const internalId = React.useId()\n const baseId = externalId || internalId\n const listboxId = `${baseId}-listbox`\n const optionIdPrefix = `${baseId}-option`\n\n // Controlled / uncontrolled selection\n const isControlled = value !== undefined\n const [internalValue, setInternalValue] = React.useState<AutocompleteOption | null>(defaultValue)\n const selected = isControlled ? value : internalValue\n\n const [query, setQuery] = React.useState(selected?.label ?? '')\n const [isOpen, setIsOpen] = React.useState(false)\n const [highlightedIndex, setHighlightedIndex] = React.useState(-1)\n const internalRef = React.useRef<HTMLInputElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const floatingRef = React.useRef<HTMLUListElement>(null)\n const { reducedMotion } = useMotion()\n\n // Floating UI — anchor the dropdown to the whole field (container width)\n const { refs, floatingStyles } = useFloating({\n open: isOpen,\n placement: 'bottom-start',\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(4),\n flip(),\n shift(),\n sizeMiddleware({\n apply({ availableHeight, rects, elements }) {\n Object.assign(elements.floating.style, {\n maxHeight: `${Math.min(availableHeight, 240)}px`,\n width: `${rects.reference.width}px`,\n })\n },\n }),\n ],\n })\n\n const setContainer = React.useCallback(\n (node: HTMLDivElement | null) => {\n containerRef.current = node\n refs.setReference(node)\n },\n [refs],\n )\n\n const composedInputRef = React.useCallback(\n (node: HTMLInputElement | null) => {\n internalRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node\n },\n [ref],\n )\n\n const setFloating = React.useCallback(\n (node: HTMLUListElement | null) => {\n floatingRef.current = node\n refs.setFloating(node)\n },\n [refs],\n )\n\n // Sync query when the selection changes externally\n React.useEffect(() => {\n setQuery(selected?.label ?? '')\n }, [selected])\n\n const filtered = React.useMemo(\n () =>\n query\n ? options.filter((o) => o.label.toLowerCase().includes(query.toLowerCase()))\n : options,\n [options, query],\n )\n\n const handleSelect = React.useCallback(\n (option: AutocompleteOption) => {\n setQuery(option.label)\n setIsOpen(false)\n setHighlightedIndex(-1)\n if (!isControlled) setInternalValue(option)\n onValueChange?.(option)\n },\n [isControlled, onValueChange],\n )\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return\n if (!isOpen) {\n if (e.key === 'ArrowDown' || e.key === 'Enter') setIsOpen(true)\n return\n }\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault()\n setHighlightedIndex((i) => Math.min(i + 1, filtered.length - 1))\n break\n case 'ArrowUp':\n e.preventDefault()\n setHighlightedIndex((i) => Math.max(i - 1, 0))\n break\n case 'Home':\n e.preventDefault()\n setHighlightedIndex(0)\n break\n case 'End':\n e.preventDefault()\n setHighlightedIndex(filtered.length - 1)\n break\n case 'Enter':\n e.preventDefault()\n if (highlightedIndex >= 0 && filtered[highlightedIndex]) handleSelect(filtered[highlightedIndex])\n break\n case 'Escape':\n setIsOpen(false)\n setHighlightedIndex(-1)\n break\n }\n },\n [disabled, isOpen, filtered, highlightedIndex, handleSelect],\n )\n\n // FormField wiring is auto-consumed by Input; only used here to decide the\n // standalone accessible-name fallback + the combobox aria-controls id.\n const fieldCtx = useFormField()\n const hasFieldName = !!(externalId || fieldCtx.inputId)\n const highlightedOptionId = highlightedIndex >= 0 ? `${optionIdPrefix}-${highlightedIndex}` : undefined\n\n return (\n <div ref={setContainer} className={cn('relative', className)} {...props}>\n <Input\n ref={composedInputRef}\n type=\"text\"\n role=\"combobox\"\n size={size}\n state={state}\n id={externalId}\n value={query}\n placeholder={placeholder}\n disabled={disabled}\n aria-expanded={isOpen}\n aria-autocomplete=\"list\"\n aria-controls={isOpen ? listboxId : undefined}\n aria-activedescendant={highlightedOptionId}\n aria-label={hasFieldName ? undefined : placeholder}\n endSection={isLoading ? <Spinner size=\"sm\" /> : undefined}\n onChange={(e) => {\n setQuery(e.target.value)\n setIsOpen(true)\n setHighlightedIndex(-1)\n }}\n onFocus={() => setIsOpen(true)}\n onBlur={(e) => {\n const target = e.relatedTarget as Node | null\n if (!containerRef.current?.contains(target) && !floatingRef.current?.contains(target)) {\n setIsOpen(false)\n }\n }}\n onKeyDown={handleKeyDown}\n />\n {typeof document !== 'undefined' &&\n createPortal(\n <AnimatePresence>\n {isOpen && (\n <motion.ul\n id={listboxId}\n ref={setFloating}\n role=\"listbox\"\n initial={reducedMotion ? false : { opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={reducedMotion ? { opacity: 1 } : { opacity: 0 }}\n transition={reducedMotion ? { duration: 0 } : tweens.fade}\n style={floatingStyles}\n className=\"z-popover overflow-auto rounded-overlay bg-surface-overlay shadow-raised-hover\"\n >\n {isLoading ? (\n <li className=\"flex items-center gap-ds-03 px-ds-04 py-ds-03 text-body-md text-surface-fg-muted\">\n <Spinner size=\"sm\" />\n {loadingText}\n </li>\n ) : filtered.length === 0 ? (\n <li className=\"px-ds-04 py-ds-03 text-body-md text-surface-fg-muted\">{emptyText}</li>\n ) : (\n filtered.map((option, index) => (\n // eslint-disable-next-line jsx-a11y/click-events-have-key-events -- WAI-ARIA combobox: keyboard nav is on the input via aria-activedescendant; options are pointer affordances, not focusable tab stops\n <li\n key={option.value}\n id={`${optionIdPrefix}-${index}`}\n role=\"option\"\n aria-selected={highlightedIndex === index}\n title={option.label}\n className={cn(\n 'cursor-pointer truncate px-ds-04 py-ds-03 text-body-md text-surface-fg transition-colors duration-fast-01',\n highlightedIndex === index && 'bg-accent-3',\n selected?.value === option.value && 'font-semibold',\n )}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => handleSelect(option)}\n onMouseEnter={() => setHighlightedIndex(index)}\n >\n {renderOption ? renderOption(option, query) : <HighlightMatch label={option.label} query={query} />}\n </li>\n ))\n )}\n </motion.ul>\n )}\n </AnimatePresence>,\n document.body,\n )}\n </div>\n )\n },\n)\nAutocomplete.displayName = 'Autocomplete'\n\nexport { Autocomplete }\n"],"mappings":"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"}
|
package/dist/ui/combobox.d.ts
CHANGED
|
@@ -98,6 +98,6 @@ interface ComboboxMultipleProps extends ComboboxBaseProps {
|
|
|
98
98
|
onValueChange: (value: string[]) => void;
|
|
99
99
|
}
|
|
100
100
|
export type ComboboxProps = ComboboxSingleProps | ComboboxMultipleProps;
|
|
101
|
-
declare const Combobox: React.ForwardRefExoticComponent<ComboboxProps & React.RefAttributes<
|
|
101
|
+
declare const Combobox: React.ForwardRefExoticComponent<ComboboxProps & React.RefAttributes<HTMLDivElement>>;
|
|
102
102
|
export { Combobox };
|
|
103
103
|
//# sourceMappingURL=combobox.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combobox.d.ts","sourceRoot":"","sources":["../../src/ui/combobox.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,mBAAmB,CAAA;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAYjD,eAAO,MAAM,uBAAuB;;
|
|
1
|
+
{"version":3,"file":"combobox.d.ts","sourceRoot":"","sources":["../../src/ui/combobox.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,mBAAmB,CAAA;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAYjD,eAAO,MAAM,uBAAuB;;8EAsBnC,CAAA;AAED,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,YAAY,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;AA0B5F;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,UAAU,iBAAkB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IACxF,+CAA+C;IAC/C,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oGAAoG;IACpG,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAA;IAC7E,4FAA4F;IAC5F,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,iFAAiF;IACjF,IAAI,CAAC,EAAE,YAAY,CAAA;IACnB,8GAA8G;IAC9G,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB;AAED,UAAU,mBAAoB,SAAQ,iBAAiB;IACrD,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACvC;AAED,UAAU,qBAAsB,SAAQ,iBAAiB;IACvD,QAAQ,EAAE,IAAI,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;CACzC;AAED,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,qBAAqB,CAAA;AAQvE,QAAA,MAAM,QAAQ,sFAyab,CAAA;AAGD,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|