@ttt-productions/ui-core 0.11.20 → 0.11.22

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.
@@ -1,18 +1,23 @@
1
+ import { type DeclaredInputFormat } from '@ttt-productions/input-format-core';
1
2
  /**
2
- * The inline required-reason config. The caller OWNS the reason value/onChange (draft state lives at
3
- * the call site). Any `minLength`/`maxLength` the caller supplies MUST come from a caller-owned
4
- * constant — this component declares no business numbers and no copy; it only derives the gate + the
5
- * Textarea `maxLength` from the values it is handed.
3
+ * The inline reason field. The caller OWNS the reason value/onChange (draft state lives at the call
4
+ * site) and the field's declaration — this component declares no business numbers and no copy.
5
+ * Confirm stays disabled until the value passes the declaration's check; `min: 0` makes the reason
6
+ * optional.
6
7
  */
7
8
  export interface ConsequenceDialogReason {
8
- required?: boolean;
9
- minLength?: number;
10
- maxLength?: number;
9
+ inputFormat: DeclaredInputFormat;
11
10
  placeholder?: string;
12
11
  label?: React.ReactNode;
13
12
  value: string;
14
13
  onChange: (value: string) => void;
15
14
  rows?: number;
15
+ /**
16
+ * A line rendered directly under the reason box, such as why its current text is refused. It is
17
+ * called with the id the box names in its `aria-describedby`, and the element it renders carries
18
+ * that id. The caller owns the words; this component adds none.
19
+ */
20
+ hint?: (id: string) => React.ReactNode;
16
21
  }
17
22
  /**
18
23
  * The exact-match typed-confirmation gate. `phrase` is business copy owned by the CALLER; the
@@ -36,7 +41,7 @@ export interface ConsequenceDialogProps {
36
41
  delayedEffect?: React.ReactNode;
37
42
  /** SLOT 3 — whether/how this can be undone or superseded. */
38
43
  reversibility?: React.ReactNode;
39
- /** Inline required-reason field. Optional. */
44
+ /** Inline reason field. Optional. */
40
45
  reason?: ConsequenceDialogReason;
41
46
  /** Exact-phrase typed-confirmation gate. Optional. */
42
47
  typedConfirmation?: ConsequenceDialogTypedConfirmation;
@@ -55,7 +60,7 @@ export interface ConsequenceDialogProps {
55
60
  }
56
61
  /**
57
62
  * A reusable confirm-before-a-consequential-action dialog. Three optional, consistently-styled
58
- * consequence slots (immediate / afterward / reversibility), an optional inline required-reason field,
63
+ * consequence slots (immediate / afterward / reversibility), an optional inline reason field,
59
64
  * and an optional exact-phrase typed-confirmation gate. Composes the ui-core AlertDialog primitives
60
65
  * (Radix) — it never re-implements them. Supports BOTH a `trigger` (uncontrolled) and `open` /
61
66
  * `onOpenChange` (controlled).
@@ -1 +1 @@
1
- {"version":3,"file":"consequence-dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/consequence-dialog.tsx"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,kCAAkC;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,uFAAuF;IACvF,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,0DAA0D;IAC1D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAEvC,6EAA6E;IAC7E,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IAEvB,oFAAoF;IACpF,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAClC,wFAAwF;IACxF,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,6DAA6D;IAC7D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAEhC,8CAA8C;IAC9C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,kCAAkC,CAAC;IAEvD,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AA+BD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,eAAe,EACf,aAAa,EACb,aAAa,EACb,MAAM,EACN,iBAAiB,EACjB,YAAY,EACZ,WAAsB,EACtB,WAAmB,EACnB,SAAS,GACV,EAAE,sBAAsB,+BAkJxB"}
1
+ {"version":3,"file":"consequence-dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/consequence-dialog.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAuC,KAAK,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAGnH;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,mBAAmB,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CACxC;AAED;;;GAGG;AACH,MAAM,WAAW,kCAAkC;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,uFAAuF;IACvF,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,0DAA0D;IAC1D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAEvC,6EAA6E;IAC7E,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IAEvB,oFAAoF;IACpF,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAClC,wFAAwF;IACxF,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,6DAA6D;IAC7D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAEhC,qCAAqC;IACrC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,kCAAkC,CAAC;IAEvD,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AA+BD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,eAAe,EACf,aAAa,EACb,aAAa,EACb,MAAM,EACN,iBAAiB,EACjB,YAAY,EACZ,WAAsB,EACtB,WAAmB,EACnB,SAAS,GACV,EAAE,sBAAsB,+BAmJxB"}
@@ -1,11 +1,12 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useCallback, useId, useState } from 'react';
3
+ import { useCallback, useId, useMemo, useState } from 'react';
4
4
  import { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, } from './alert-dialog.js';
5
5
  import { Button } from './button.js';
6
6
  import { Textarea } from './textarea.js';
7
7
  import { Input } from './input.js';
8
8
  import { Label } from './label.js';
9
+ import { checkInputFormat, defineInputFormat } from '@ttt-productions/input-format-core';
9
10
  import { Zap, Clock, RotateCcw } from 'lucide-react';
10
11
  const SLOT_ICON_CLASS = 'icon-xs mt-0.5 shrink-0 text-muted-foreground';
11
12
  /**
@@ -18,7 +19,7 @@ function ConsequenceRow({ icon: Icon, label, children, }) {
18
19
  }
19
20
  /**
20
21
  * A reusable confirm-before-a-consequential-action dialog. Three optional, consistently-styled
21
- * consequence slots (immediate / afterward / reversibility), an optional inline required-reason field,
22
+ * consequence slots (immediate / afterward / reversibility), an optional inline reason field,
22
23
  * and an optional exact-phrase typed-confirmation gate. Composes the ui-core AlertDialog primitives
23
24
  * (Radix) — it never re-implements them. Supports BOTH a `trigger` (uncontrolled) and `open` /
24
25
  * `onOpenChange` (controlled).
@@ -34,6 +35,7 @@ export function ConsequenceDialog({ trigger, open, onOpenChange, title, immediat
34
35
  const [pending, setPending] = useState(false);
35
36
  const actualOpen = isControlled ? open : internalOpen;
36
37
  const reasonId = useId();
38
+ const reasonHintId = useId();
37
39
  const typedId = useId();
38
40
  const setOpenState = useCallback((next) => {
39
41
  if (isControlled)
@@ -65,20 +67,14 @@ export function ConsequenceDialog({ trigger, open, onOpenChange, title, immediat
65
67
  setPending(false);
66
68
  }
67
69
  }, [pending, onConfirm, setOpenState]);
68
- const reasonTrimmedLength = reason ? reason.value.trim().length : 0;
69
- const reasonProvided = reasonTrimmedLength > 0;
70
- const reasonOk = !reason
71
- ? true
72
- : reason.required && !reasonProvided
73
- ? false
74
- : !reasonProvided
75
- ? true // optional + empty is fine
76
- : (reason.minLength === undefined || reasonTrimmedLength >= reason.minLength) &&
77
- (reason.maxLength === undefined || reason.value.length <= reason.maxLength);
70
+ const reasonOk = !reason || checkInputFormat(reason.value, reason.inputFormat).ok;
78
71
  const typedOk = !typedConfirmation ? true : typedValue === typedConfirmation.phrase;
72
+ // The phrase is one line, and nothing longer than it can ever match it.
73
+ const phrase = typedConfirmation?.phrase ?? '';
74
+ const typedFormat = useMemo(() => (phrase.length > 0 ? defineInputFormat({ format: 'singleLine', min: 1, max: phrase.length }) : null), [phrase]);
79
75
  return (_jsxs(AlertDialog, { open: actualOpen, onOpenChange: handleOpenChange, children: [trigger ? _jsx(AlertDialogTrigger, { asChild: true, children: trigger }) : null, _jsxs(AlertDialogContent, { onEscapeKeyDown: (e) => {
80
76
  if (pending)
81
77
  e.preventDefault();
82
- }, children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: title }), _jsx(AlertDialogDescription, { asChild: true, children: _jsxs("div", { className: "stack-3 text-left", children: [immediateEffect ? (_jsx(ConsequenceRow, { icon: Zap, label: "Immediately", children: immediateEffect })) : null, delayedEffect ? (_jsx(ConsequenceRow, { icon: Clock, label: "Afterward", children: delayedEffect })) : null, reversibility ? (_jsx(ConsequenceRow, { icon: RotateCcw, label: "Reversibility", children: reversibility })) : null] }) })] }), reason ? (_jsxs("div", { className: "stack-2", children: [_jsx(Label, { htmlFor: reasonId, children: reason.label ?? 'Reason' }), _jsx(Textarea, { id: reasonId, value: reason.value, onChange: (e) => reason.onChange(e.target.value), placeholder: reason.placeholder, maxLength: reason.maxLength, rows: reason.rows ?? 3, disabled: pending })] })) : null, typedConfirmation ? (_jsxs("div", { className: "stack-2", children: [_jsx(Label, { htmlFor: typedId, children: typedConfirmation.label ?? (_jsxs(_Fragment, { children: ["Type ", _jsx("span", { className: "font-mono", children: typedConfirmation.phrase }), " to confirm"] })) }), _jsx(Input, { id: typedId, value: typedValue, onChange: (e) => setTypedValue(e.target.value), placeholder: typedConfirmation.phrase, disabled: pending, autoComplete: "off" })] })) : null, _jsxs(AlertDialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => handleOpenChange(false), disabled: pending, children: cancelLabel }), _jsx(Button, { variant: destructive ? 'destructive' : 'default', onClick: handleConfirm, disabled: !reasonOk || !typedOk, pending: pending, children: confirmLabel })] })] })] }));
78
+ }, children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: title }), _jsx(AlertDialogDescription, { asChild: true, children: _jsxs("div", { className: "stack-3 text-left", children: [immediateEffect ? (_jsx(ConsequenceRow, { icon: Zap, label: "Immediately", children: immediateEffect })) : null, delayedEffect ? (_jsx(ConsequenceRow, { icon: Clock, label: "Afterward", children: delayedEffect })) : null, reversibility ? (_jsx(ConsequenceRow, { icon: RotateCcw, label: "Reversibility", children: reversibility })) : null] }) })] }), reason ? (_jsxs("div", { className: "stack-2", children: [_jsx(Label, { htmlFor: reasonId, children: reason.label ?? 'Reason' }), _jsx(Textarea, { id: reasonId, value: reason.value, onChange: (e) => reason.onChange(e.target.value), placeholder: reason.placeholder, inputFormat: reason.inputFormat, rows: reason.rows ?? 3, disabled: pending, "aria-describedby": reason.hint ? reasonHintId : undefined }), reason.hint ? reason.hint(reasonHintId) : null] })) : null, typedConfirmation && typedFormat ? (_jsxs("div", { className: "stack-2", children: [_jsx(Label, { htmlFor: typedId, children: typedConfirmation.label ?? (_jsxs(_Fragment, { children: ["Type ", _jsx("span", { className: "font-mono", children: typedConfirmation.phrase }), " to confirm"] })) }), _jsx(Input, { id: typedId, value: typedValue, onChange: (e) => setTypedValue(e.target.value), placeholder: typedConfirmation.phrase, inputFormat: typedFormat, disabled: pending, autoComplete: "off" })] })) : null, _jsxs(AlertDialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => handleOpenChange(false), disabled: pending, children: cancelLabel }), _jsx(Button, { variant: destructive ? 'destructive' : 'default', onClick: handleConfirm, disabled: !reasonOk || !typedOk, pending: pending, children: confirmLabel })] })] })] }));
83
79
  }
84
80
  //# sourceMappingURL=consequence-dialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"consequence-dialog.js","sourceRoot":"","sources":["../../../src/react/components/consequence-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useId, useState } from 'react';\nimport {\n AlertDialog,\n AlertDialogTrigger,\n AlertDialogContent,\n AlertDialogHeader,\n AlertDialogFooter,\n AlertDialogTitle,\n AlertDialogDescription,\n} from './alert-dialog.js';\nimport { Button } from './button.js';\nimport { Textarea } from './textarea.js';\nimport { Input } from './input.js';\nimport { Label } from './label.js';\nimport { Zap, Clock, RotateCcw } from 'lucide-react';\n\n/**\n * The inline required-reason config. The caller OWNS the reason value/onChange (draft state lives at\n * the call site). Any `minLength`/`maxLength` the caller supplies MUST come from a caller-owned\n * constant — this component declares no business numbers and no copy; it only derives the gate + the\n * Textarea `maxLength` from the values it is handed.\n */\nexport interface ConsequenceDialogReason {\n required?: boolean;\n minLength?: number;\n maxLength?: number;\n placeholder?: string;\n label?: React.ReactNode;\n value: string;\n onChange: (value: string) => void;\n rows?: number;\n}\n\n/**\n * The exact-match typed-confirmation gate. `phrase` is business copy owned by the CALLER; the\n * component only compares the typed value against it.\n */\nexport interface ConsequenceDialogTypedConfirmation {\n phrase: string;\n label?: React.ReactNode;\n}\n\nexport interface ConsequenceDialogProps {\n /** Uncontrolled open model: the element that opens the dialog (rendered `asChild`). */\n trigger?: React.ReactNode;\n /** Controlled open model. Provide with `onOpenChange`. */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n\n /** Heading — a string or node so callers can interpolate amounts / names. */\n title: React.ReactNode;\n\n /** SLOT 1 — what happens right now (e.g. \"initiates a Stripe refund of $X now\"). */\n immediateEffect?: React.ReactNode;\n /** SLOT 2 — what settles later, out of band (webhook / worker / async confirmation). */\n delayedEffect?: React.ReactNode;\n /** SLOT 3 — whether/how this can be undone or superseded. */\n reversibility?: React.ReactNode;\n\n /** Inline required-reason field. Optional. */\n reason?: ConsequenceDialogReason;\n /** Exact-phrase typed-confirmation gate. Optional. */\n typedConfirmation?: ConsequenceDialogTypedConfirmation;\n\n confirmLabel: React.ReactNode;\n cancelLabel?: React.ReactNode;\n /** `true` styles the confirm button with `bg-destructive`. */\n destructive?: boolean;\n\n /**\n * Runs on confirm. May be async — while it is pending the dialog STAYS OPEN, both buttons disable,\n * and the confirm button shows its pending spinner. Closes on resolve. If it rejects the dialog\n * stays open so the caller's own surface (error toast, TOTP step-up) can show and the operator can\n * retry or cancel. This component takes no Sentry/monitoring dependency — every error path\n * belongs to the caller's `onConfirm`.\n */\n onConfirm: () => void | Promise<void>;\n}\n\nconst SLOT_ICON_CLASS = 'icon-xs mt-0.5 shrink-0 text-muted-foreground';\n\n/**\n * One consequence slot: the icon beside a block column whose label sits on its own line above\n * the slot text. Every element is a block: the `stack-*` spacing is a vertical margin, which an\n * inline element ignores.\n */\nfunction ConsequenceRow({\n icon: Icon,\n label,\n children,\n}: {\n icon: typeof Zap;\n label: string;\n children: React.ReactNode;\n}) {\n return (\n <div className=\"flex items-start gap-2\">\n <Icon className={SLOT_ICON_CLASS} aria-hidden=\"true\" />\n <div className=\"stack-1 min-w-0\">\n <div className=\"text-caption font-medium uppercase tracking-wide text-muted-foreground\">\n {label}\n </div>\n <div className=\"text-small text-foreground\">{children}</div>\n </div>\n </div>\n );\n}\n\n/**\n * A reusable confirm-before-a-consequential-action dialog. Three optional, consistently-styled\n * consequence slots (immediate / afterward / reversibility), an optional inline required-reason field,\n * and an optional exact-phrase typed-confirmation gate. Composes the ui-core AlertDialog primitives\n * (Radix) — it never re-implements them. Supports BOTH a `trigger` (uncontrolled) and `open` /\n * `onOpenChange` (controlled).\n *\n * Pending model: the confirm button is a plain `Button` (NOT `AlertDialogAction`), so Radix does not\n * auto-close on click; open state is managed here so the dialog stays open with a visible spinner\n * until `onConfirm` resolves.\n */\nexport function ConsequenceDialog({\n trigger,\n open,\n onOpenChange,\n title,\n immediateEffect,\n delayedEffect,\n reversibility,\n reason,\n typedConfirmation,\n confirmLabel,\n cancelLabel = 'Cancel',\n destructive = false,\n onConfirm,\n}: ConsequenceDialogProps) {\n const isControlled = open !== undefined;\n const [internalOpen, setInternalOpen] = useState(false);\n const [typedValue, setTypedValue] = useState('');\n const [pending, setPending] = useState(false);\n\n const actualOpen = isControlled ? open : internalOpen;\n\n const reasonId = useId();\n const typedId = useId();\n\n const setOpenState = useCallback(\n (next: boolean) => {\n if (isControlled) onOpenChange?.(next);\n else setInternalOpen(next);\n },\n [isControlled, onOpenChange],\n );\n\n // Never allow an open-state change (Escape, cancel, controlled parent) to fight the pending action.\n const handleOpenChange = useCallback(\n (next: boolean) => {\n if (pending) return;\n setTypedValue(''); // reset the typed gate on every open/close\n setOpenState(next);\n },\n [pending, setOpenState],\n );\n\n const handleConfirm = useCallback(async () => {\n if (pending) return;\n setPending(true);\n try {\n await onConfirm();\n setTypedValue('');\n setOpenState(false);\n } catch {\n // Keep the dialog open — the caller's onConfirm surfaced the error (toast / step-up). The\n // operator can retry or cancel. Never rethrow, never monitor here.\n } finally {\n setPending(false);\n }\n }, [pending, onConfirm, setOpenState]);\n\n const reasonTrimmedLength = reason ? reason.value.trim().length : 0;\n const reasonProvided = reasonTrimmedLength > 0;\n const reasonOk = !reason\n ? true\n : reason.required && !reasonProvided\n ? false\n : !reasonProvided\n ? true // optional + empty is fine\n : (reason.minLength === undefined || reasonTrimmedLength >= reason.minLength) &&\n (reason.maxLength === undefined || reason.value.length <= reason.maxLength);\n\n const typedOk = !typedConfirmation ? true : typedValue === typedConfirmation.phrase;\n\n return (\n <AlertDialog open={actualOpen} onOpenChange={handleOpenChange}>\n {trigger ? <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger> : null}\n <AlertDialogContent\n onEscapeKeyDown={(e) => {\n if (pending) e.preventDefault();\n }}\n >\n <AlertDialogHeader>\n <AlertDialogTitle>{title}</AlertDialogTitle>\n {/* A div, not the primitive's default <p>: the rows are blocks, which a <p> cannot hold.\n Left-aligned at every width — the header centres its text on narrow screens, and a\n wrapped consequence line reads as a list, not a caption. */}\n <AlertDialogDescription asChild>\n <div className=\"stack-3 text-left\">\n {immediateEffect ? (\n <ConsequenceRow icon={Zap} label=\"Immediately\">\n {immediateEffect}\n </ConsequenceRow>\n ) : null}\n {delayedEffect ? (\n <ConsequenceRow icon={Clock} label=\"Afterward\">\n {delayedEffect}\n </ConsequenceRow>\n ) : null}\n {reversibility ? (\n <ConsequenceRow icon={RotateCcw} label=\"Reversibility\">\n {reversibility}\n </ConsequenceRow>\n ) : null}\n </div>\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n {reason ? (\n <div className=\"stack-2\">\n <Label htmlFor={reasonId}>{reason.label ?? 'Reason'}</Label>\n <Textarea\n id={reasonId}\n value={reason.value}\n onChange={(e) => reason.onChange(e.target.value)}\n placeholder={reason.placeholder}\n maxLength={reason.maxLength}\n rows={reason.rows ?? 3}\n disabled={pending}\n />\n </div>\n ) : null}\n\n {typedConfirmation ? (\n <div className=\"stack-2\">\n <Label htmlFor={typedId}>\n {typedConfirmation.label ?? (\n <>\n Type <span className=\"font-mono\">{typedConfirmation.phrase}</span> to confirm\n </>\n )}\n </Label>\n <Input\n id={typedId}\n value={typedValue}\n onChange={(e) => setTypedValue(e.target.value)}\n placeholder={typedConfirmation.phrase}\n disabled={pending}\n autoComplete=\"off\"\n />\n </div>\n ) : null}\n\n <AlertDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={pending}\n >\n {cancelLabel}\n </Button>\n <Button\n variant={destructive ? 'destructive' : 'default'}\n onClick={handleConfirm}\n disabled={!reasonOk || !typedOk}\n pending={pending}\n >\n {confirmLabel}\n </Button>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"]}
1
+ {"version":3,"file":"consequence-dialog.js","sourceRoot":"","sources":["../../../src/react/components/consequence-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useId, useMemo, useState } from 'react';\nimport {\n AlertDialog,\n AlertDialogTrigger,\n AlertDialogContent,\n AlertDialogHeader,\n AlertDialogFooter,\n AlertDialogTitle,\n AlertDialogDescription,\n} from './alert-dialog.js';\nimport { Button } from './button.js';\nimport { Textarea } from './textarea.js';\nimport { Input } from './input.js';\nimport { Label } from './label.js';\nimport { checkInputFormat, defineInputFormat, type DeclaredInputFormat } from '@ttt-productions/input-format-core';\nimport { Zap, Clock, RotateCcw } from 'lucide-react';\n\n/**\n * The inline reason field. The caller OWNS the reason value/onChange (draft state lives at the call\n * site) and the field's declaration — this component declares no business numbers and no copy.\n * Confirm stays disabled until the value passes the declaration's check; `min: 0` makes the reason\n * optional.\n */\nexport interface ConsequenceDialogReason {\n inputFormat: DeclaredInputFormat;\n placeholder?: string;\n label?: React.ReactNode;\n value: string;\n onChange: (value: string) => void;\n rows?: number;\n /**\n * A line rendered directly under the reason box, such as why its current text is refused. It is\n * called with the id the box names in its `aria-describedby`, and the element it renders carries\n * that id. The caller owns the words; this component adds none.\n */\n hint?: (id: string) => React.ReactNode;\n}\n\n/**\n * The exact-match typed-confirmation gate. `phrase` is business copy owned by the CALLER; the\n * component only compares the typed value against it.\n */\nexport interface ConsequenceDialogTypedConfirmation {\n phrase: string;\n label?: React.ReactNode;\n}\n\nexport interface ConsequenceDialogProps {\n /** Uncontrolled open model: the element that opens the dialog (rendered `asChild`). */\n trigger?: React.ReactNode;\n /** Controlled open model. Provide with `onOpenChange`. */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n\n /** Heading — a string or node so callers can interpolate amounts / names. */\n title: React.ReactNode;\n\n /** SLOT 1 — what happens right now (e.g. \"initiates a Stripe refund of $X now\"). */\n immediateEffect?: React.ReactNode;\n /** SLOT 2 — what settles later, out of band (webhook / worker / async confirmation). */\n delayedEffect?: React.ReactNode;\n /** SLOT 3 — whether/how this can be undone or superseded. */\n reversibility?: React.ReactNode;\n\n /** Inline reason field. Optional. */\n reason?: ConsequenceDialogReason;\n /** Exact-phrase typed-confirmation gate. Optional. */\n typedConfirmation?: ConsequenceDialogTypedConfirmation;\n\n confirmLabel: React.ReactNode;\n cancelLabel?: React.ReactNode;\n /** `true` styles the confirm button with `bg-destructive`. */\n destructive?: boolean;\n\n /**\n * Runs on confirm. May be async — while it is pending the dialog STAYS OPEN, both buttons disable,\n * and the confirm button shows its pending spinner. Closes on resolve. If it rejects the dialog\n * stays open so the caller's own surface (error toast, TOTP step-up) can show and the operator can\n * retry or cancel. This component takes no Sentry/monitoring dependency — every error path\n * belongs to the caller's `onConfirm`.\n */\n onConfirm: () => void | Promise<void>;\n}\n\nconst SLOT_ICON_CLASS = 'icon-xs mt-0.5 shrink-0 text-muted-foreground';\n\n/**\n * One consequence slot: the icon beside a block column whose label sits on its own line above\n * the slot text. Every element is a block: the `stack-*` spacing is a vertical margin, which an\n * inline element ignores.\n */\nfunction ConsequenceRow({\n icon: Icon,\n label,\n children,\n}: {\n icon: typeof Zap;\n label: string;\n children: React.ReactNode;\n}) {\n return (\n <div className=\"flex items-start gap-2\">\n <Icon className={SLOT_ICON_CLASS} aria-hidden=\"true\" />\n <div className=\"stack-1 min-w-0\">\n <div className=\"text-caption font-medium uppercase tracking-wide text-muted-foreground\">\n {label}\n </div>\n <div className=\"text-small text-foreground\">{children}</div>\n </div>\n </div>\n );\n}\n\n/**\n * A reusable confirm-before-a-consequential-action dialog. Three optional, consistently-styled\n * consequence slots (immediate / afterward / reversibility), an optional inline reason field,\n * and an optional exact-phrase typed-confirmation gate. Composes the ui-core AlertDialog primitives\n * (Radix) — it never re-implements them. Supports BOTH a `trigger` (uncontrolled) and `open` /\n * `onOpenChange` (controlled).\n *\n * Pending model: the confirm button is a plain `Button` (NOT `AlertDialogAction`), so Radix does not\n * auto-close on click; open state is managed here so the dialog stays open with a visible spinner\n * until `onConfirm` resolves.\n */\nexport function ConsequenceDialog({\n trigger,\n open,\n onOpenChange,\n title,\n immediateEffect,\n delayedEffect,\n reversibility,\n reason,\n typedConfirmation,\n confirmLabel,\n cancelLabel = 'Cancel',\n destructive = false,\n onConfirm,\n}: ConsequenceDialogProps) {\n const isControlled = open !== undefined;\n const [internalOpen, setInternalOpen] = useState(false);\n const [typedValue, setTypedValue] = useState('');\n const [pending, setPending] = useState(false);\n\n const actualOpen = isControlled ? open : internalOpen;\n\n const reasonId = useId();\n const reasonHintId = useId();\n const typedId = useId();\n\n const setOpenState = useCallback(\n (next: boolean) => {\n if (isControlled) onOpenChange?.(next);\n else setInternalOpen(next);\n },\n [isControlled, onOpenChange],\n );\n\n // Never allow an open-state change (Escape, cancel, controlled parent) to fight the pending action.\n const handleOpenChange = useCallback(\n (next: boolean) => {\n if (pending) return;\n setTypedValue(''); // reset the typed gate on every open/close\n setOpenState(next);\n },\n [pending, setOpenState],\n );\n\n const handleConfirm = useCallback(async () => {\n if (pending) return;\n setPending(true);\n try {\n await onConfirm();\n setTypedValue('');\n setOpenState(false);\n } catch {\n // Keep the dialog open — the caller's onConfirm surfaced the error (toast / step-up). The\n // operator can retry or cancel. Never rethrow, never monitor here.\n } finally {\n setPending(false);\n }\n }, [pending, onConfirm, setOpenState]);\n\n const reasonOk = !reason || checkInputFormat(reason.value, reason.inputFormat).ok;\n\n const typedOk = !typedConfirmation ? true : typedValue === typedConfirmation.phrase;\n // The phrase is one line, and nothing longer than it can ever match it.\n const phrase = typedConfirmation?.phrase ?? '';\n const typedFormat = useMemo(\n () => (phrase.length > 0 ? defineInputFormat({ format: 'singleLine', min: 1, max: phrase.length }) : null),\n [phrase],\n );\n\n return (\n <AlertDialog open={actualOpen} onOpenChange={handleOpenChange}>\n {trigger ? <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger> : null}\n <AlertDialogContent\n onEscapeKeyDown={(e) => {\n if (pending) e.preventDefault();\n }}\n >\n <AlertDialogHeader>\n <AlertDialogTitle>{title}</AlertDialogTitle>\n {/* A div, not the primitive's default <p>: the rows are blocks, which a <p> cannot hold.\n Left-aligned at every width — the header centres its text on narrow screens, and a\n wrapped consequence line reads as a list, not a caption. */}\n <AlertDialogDescription asChild>\n <div className=\"stack-3 text-left\">\n {immediateEffect ? (\n <ConsequenceRow icon={Zap} label=\"Immediately\">\n {immediateEffect}\n </ConsequenceRow>\n ) : null}\n {delayedEffect ? (\n <ConsequenceRow icon={Clock} label=\"Afterward\">\n {delayedEffect}\n </ConsequenceRow>\n ) : null}\n {reversibility ? (\n <ConsequenceRow icon={RotateCcw} label=\"Reversibility\">\n {reversibility}\n </ConsequenceRow>\n ) : null}\n </div>\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n {reason ? (\n <div className=\"stack-2\">\n <Label htmlFor={reasonId}>{reason.label ?? 'Reason'}</Label>\n <Textarea\n id={reasonId}\n value={reason.value}\n onChange={(e) => reason.onChange(e.target.value)}\n placeholder={reason.placeholder}\n inputFormat={reason.inputFormat}\n rows={reason.rows ?? 3}\n disabled={pending}\n aria-describedby={reason.hint ? reasonHintId : undefined}\n />\n {reason.hint ? reason.hint(reasonHintId) : null}\n </div>\n ) : null}\n\n {typedConfirmation && typedFormat ? (\n <div className=\"stack-2\">\n <Label htmlFor={typedId}>\n {typedConfirmation.label ?? (\n <>\n Type <span className=\"font-mono\">{typedConfirmation.phrase}</span> to confirm\n </>\n )}\n </Label>\n <Input\n id={typedId}\n value={typedValue}\n onChange={(e) => setTypedValue(e.target.value)}\n placeholder={typedConfirmation.phrase}\n inputFormat={typedFormat}\n disabled={pending}\n autoComplete=\"off\"\n />\n </div>\n ) : null}\n\n <AlertDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={pending}\n >\n {cancelLabel}\n </Button>\n <Button\n variant={destructive ? 'destructive' : 'default'}\n onClick={handleConfirm}\n disabled={!reasonOk || !typedOk}\n pending={pending}\n >\n {confirmLabel}\n </Button>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"]}
@@ -0,0 +1,29 @@
1
+ import { type InputFormatSpec } from "@ttt-productions/input-format-core";
2
+ interface CallerAttributes {
3
+ value?: unknown;
4
+ readOnly?: boolean;
5
+ "aria-required"?: boolean | "true" | "false";
6
+ "aria-invalid"?: boolean | "true" | "false" | "grammar" | "spelling";
7
+ }
8
+ /**
9
+ * What a free-text control derives from its field declaration: the native length cap, and the
10
+ * required/invalid state assistive technology announces. A caller's own `aria-required` or
11
+ * `aria-invalid` wins — a form field passes the state its whole schema decided.
12
+ *
13
+ * The native cap counts the text as typed while the check counts it trimmed. Every value the
14
+ * field keeps can still be typed, and surrounding whitespace is dropped by the check anyway.
15
+ *
16
+ * Only a controlled value is judged, and a text that is merely too short is not marked invalid
17
+ * while it is being typed: an empty or half-typed required field is not yet an error, so "too
18
+ * short" is left to the form's submit-time validation.
19
+ *
20
+ * A read-only control shows a stored value nobody can edit there, so it is never judged: it
21
+ * announces neither required nor invalid (a stored value may legitimately fail the typed format).
22
+ */
23
+ export declare function formatControlAttributes(spec: InputFormatSpec, props: CallerAttributes): {
24
+ maxLength: number;
25
+ "aria-required": boolean | "true" | "false" | undefined;
26
+ "aria-invalid": boolean | "true" | "false" | "grammar" | "spelling" | undefined;
27
+ };
28
+ export {};
29
+ //# sourceMappingURL=format-control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-control.d.ts","sourceRoot":"","sources":["../../../src/react/components/format-control.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,oCAAoC,CAAA;AAE3F,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAA;IAC5C,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAA;CACrE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,gBAAgB;;;;EAerF"}
@@ -0,0 +1,33 @@
1
+ import { checkInputFormat } from "@ttt-productions/input-format-core";
2
+ /**
3
+ * What a free-text control derives from its field declaration: the native length cap, and the
4
+ * required/invalid state assistive technology announces. A caller's own `aria-required` or
5
+ * `aria-invalid` wins — a form field passes the state its whole schema decided.
6
+ *
7
+ * The native cap counts the text as typed while the check counts it trimmed. Every value the
8
+ * field keeps can still be typed, and surrounding whitespace is dropped by the check anyway.
9
+ *
10
+ * Only a controlled value is judged, and a text that is merely too short is not marked invalid
11
+ * while it is being typed: an empty or half-typed required field is not yet an error, so "too
12
+ * short" is left to the form's submit-time validation.
13
+ *
14
+ * A read-only control shows a stored value nobody can edit there, so it is never judged: it
15
+ * announces neither required nor invalid (a stored value may legitimately fail the typed format).
16
+ */
17
+ export function formatControlAttributes(spec, props) {
18
+ if (props.readOnly) {
19
+ return {
20
+ maxLength: spec.max,
21
+ "aria-required": props["aria-required"],
22
+ "aria-invalid": props["aria-invalid"],
23
+ };
24
+ }
25
+ const result = typeof props.value === "string" ? checkInputFormat(props.value, spec) : null;
26
+ const malformed = result !== null && !result.ok && result.issue !== "tooShort";
27
+ return {
28
+ maxLength: spec.max,
29
+ "aria-required": props["aria-required"] ?? (spec.min > 0 ? true : undefined),
30
+ "aria-invalid": props["aria-invalid"] ?? (malformed ? true : undefined),
31
+ };
32
+ }
33
+ //# sourceMappingURL=format-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-control.js","sourceRoot":"","sources":["../../../src/react/components/format-control.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAwB,MAAM,oCAAoC,CAAA;AAS3F;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAqB,EAAE,KAAuB;IACpF,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,GAAG;YACnB,eAAe,EAAE,KAAK,CAAC,eAAe,CAAC;YACvC,cAAc,EAAE,KAAK,CAAC,cAAc,CAAC;SACtC,CAAA;IACH,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC3F,MAAM,SAAS,GAAG,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,KAAK,UAAU,CAAA;IAC9E,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,GAAG;QACnB,eAAe,EAAE,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5E,cAAc,EAAE,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;KACxE,CAAA;AACH,CAAC","sourcesContent":["import { checkInputFormat, type InputFormatSpec } from \"@ttt-productions/input-format-core\"\n\ninterface CallerAttributes {\n value?: unknown\n readOnly?: boolean\n \"aria-required\"?: boolean | \"true\" | \"false\"\n \"aria-invalid\"?: boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\"\n}\n\n/**\n * What a free-text control derives from its field declaration: the native length cap, and the\n * required/invalid state assistive technology announces. A caller's own `aria-required` or\n * `aria-invalid` wins — a form field passes the state its whole schema decided.\n *\n * The native cap counts the text as typed while the check counts it trimmed. Every value the\n * field keeps can still be typed, and surrounding whitespace is dropped by the check anyway.\n *\n * Only a controlled value is judged, and a text that is merely too short is not marked invalid\n * while it is being typed: an empty or half-typed required field is not yet an error, so \"too\n * short\" is left to the form's submit-time validation.\n *\n * A read-only control shows a stored value nobody can edit there, so it is never judged: it\n * announces neither required nor invalid (a stored value may legitimately fail the typed format).\n */\nexport function formatControlAttributes(spec: InputFormatSpec, props: CallerAttributes) {\n if (props.readOnly) {\n return {\n maxLength: spec.max,\n \"aria-required\": props[\"aria-required\"],\n \"aria-invalid\": props[\"aria-invalid\"],\n }\n }\n const result = typeof props.value === \"string\" ? checkInputFormat(props.value, spec) : null\n const malformed = result !== null && !result.ok && result.issue !== \"tooShort\"\n return {\n maxLength: spec.max,\n \"aria-required\": props[\"aria-required\"] ?? (spec.min > 0 ? true : undefined),\n \"aria-invalid\": props[\"aria-invalid\"] ?? (malformed ? true : undefined),\n }\n}\n"]}
@@ -1,4 +1,41 @@
1
1
  import * as React from "react";
2
- declare const Input: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref"> & React.RefAttributes<HTMLInputElement>>;
2
+ import type { DeclaredInputFormat } from "@ttt-productions/input-format-core";
3
+ /**
4
+ * The input types whose value the browser itself formats or picks — not free text — so they take no
5
+ * input format. A free-text input (`type` omitted or `"text"`) takes one.
6
+ */
7
+ export type BuiltInFormatInputType = "password" | "email" | "number" | "tel" | "url" | "search" | "file" | "color" | "date" | "datetime-local" | "month" | "week" | "time" | "range" | "checkbox" | "radio" | "hidden" | "button" | "submit" | "reset" | "image";
8
+ type NativeInputProps = React.ComponentPropsWithoutRef<"input">;
9
+ /**
10
+ * A free-text input: its declaration is required, and its bounds and its required state come only
11
+ * from that declaration (ENG-005), so `maxLength`, `minLength`, `pattern`, and `required` are not
12
+ * props.
13
+ */
14
+ export type FreeTextInputProps = Omit<NativeInputProps, "type" | "maxLength" | "minLength" | "pattern" | "required"> & {
15
+ type?: "text";
16
+ inputFormat: DeclaredInputFormat;
17
+ textEntry?: never;
18
+ };
19
+ /**
20
+ * What a text box that is not free text holds: digits typed as text (a date-of-birth part, a share
21
+ * count, a one-time code) or an identifier (an account, document, or case id). Neither takes an
22
+ * input format; each keeps its own native `maxLength`, `pattern`, and `inputMode`, and its value is
23
+ * checked by its own rule (a whole-input number parse, the one-segment id rule).
24
+ */
25
+ export type NonFreeTextEntry = "numeric" | "identifier";
26
+ /** A text box that is not free text: no input format. */
27
+ export type NonFreeTextInputProps = Omit<NativeInputProps, "type"> & {
28
+ type?: "text";
29
+ textEntry: NonFreeTextEntry;
30
+ inputFormat?: never;
31
+ };
32
+ /** An input whose value the browser formats: no input format. */
33
+ export type BuiltInFormatInputProps = Omit<NativeInputProps, "type"> & {
34
+ type: BuiltInFormatInputType;
35
+ inputFormat?: never;
36
+ textEntry?: never;
37
+ };
38
+ export type InputProps = FreeTextInputProps | NonFreeTextInputProps | BuiltInFormatInputProps;
39
+ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
3
40
  export { Input };
4
41
  //# sourceMappingURL=input.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/react/components/input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,QAAA,MAAM,KAAK,8KAcV,CAAA;AAGD,OAAO,EAAE,KAAK,EAAE,CAAA"}
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/react/components/input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA;AAI7E;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAC9B,UAAU,GACV,OAAO,GACP,QAAQ,GACR,KAAK,GACL,KAAK,GACL,QAAQ,GACR,MAAM,GACN,OAAO,GACP,MAAM,GACN,gBAAgB,GAChB,OAAO,GACP,MAAM,GACN,MAAM,GACN,OAAO,GACP,UAAU,GACV,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,OAAO,CAAA;AAEX,KAAK,gBAAgB,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAA;AAE/D;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,gBAAgB,EAChB,MAAM,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,GAAG,UAAU,CAC5D,GAAG;IACF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,mBAAmB,CAAA;IAChC,SAAS,CAAC,EAAE,KAAK,CAAA;CAClB,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,YAAY,CAAA;AAEvD,yDAAyD;AACzD,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,GAAG;IACnE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,gBAAgB,CAAA;IAC3B,WAAW,CAAC,EAAE,KAAK,CAAA;CACpB,CAAA;AAED,iEAAiE;AACjE,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,GAAG;IACrE,IAAI,EAAE,sBAAsB,CAAA;IAC5B,WAAW,CAAC,EAAE,KAAK,CAAA;IACnB,SAAS,CAAC,EAAE,KAAK,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,kBAAkB,GAAG,qBAAqB,GAAG,uBAAuB,CAAA;AAE7F,QAAA,MAAM,KAAK,qFAgBV,CAAA;AAGD,OAAO,EAAE,KAAK,EAAE,CAAA"}
@@ -2,8 +2,11 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "../../lib/utils.js";
5
- const Input = React.forwardRef(({ className, type, ...props }, ref) => {
6
- return (_jsx("input", { type: type, className: cn("flex h-10 w-full rounded-md border-2 border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className), ref: ref, ...props }));
5
+ import { formatControlAttributes } from "./format-control.js";
6
+ const Input = React.forwardRef(
7
+ // `textEntry` only selects the prop shape; it never reaches the DOM.
8
+ ({ className, type, inputFormat, textEntry: _textEntry, ...props }, ref) => {
9
+ return (_jsx("input", { type: type, className: cn("flex h-10 w-full rounded-md border-2 border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className), ref: ref, ...props, ...(inputFormat ? formatControlAttributes(inputFormat, props) : {}) }));
7
10
  });
8
11
  Input.displayName = "Input";
9
12
  export { Input };
@@ -1 +1 @@
1
- {"version":3,"file":"input.js","sourceRoot":"","sources":["../../../src/react/components/input.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAEvC,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAC5B,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE;IACrC,OAAO,CACL,gBACE,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,EAAE,CACX,oYAAoY,EACpY,SAAS,CACV,EACD,GAAG,EAAE,GAAG,KACJ,KAAK,GACT,CACH,CAAA;AACH,CAAC,CACF,CAAA;AACD,KAAK,CAAC,WAAW,GAAG,OAAO,CAAA;AAE3B,OAAO,EAAE,KAAK,EAAE,CAAA","sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"../../lib/utils.js\"\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-10 w-full rounded-md border-2 border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n className\n )}\n ref={ref}\n {...props}\n />\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n"]}
1
+ {"version":3,"file":"input.js","sourceRoot":"","sources":["../../../src/react/components/input.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAqE7D,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU;AAC5B,qEAAqE;AACrE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE;IACzE,OAAO,CACL,gBACE,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,EAAE,CACX,oYAAoY,EACpY,SAAS,CACV,EACD,GAAG,EAAE,GAAG,KACJ,KAAK,KACL,CAAC,WAAW,CAAC,CAAC,CAAC,uBAAuB,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GACpE,CACH,CAAA;AACH,CAAC,CACF,CAAA;AACD,KAAK,CAAC,WAAW,GAAG,OAAO,CAAA;AAE3B,OAAO,EAAE,KAAK,EAAE,CAAA","sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport type { DeclaredInputFormat } from \"@ttt-productions/input-format-core\"\nimport { cn } from \"../../lib/utils.js\"\nimport { formatControlAttributes } from \"./format-control.js\"\n\n/**\n * The input types whose value the browser itself formats or picks — not free text — so they take no\n * input format. A free-text input (`type` omitted or `\"text\"`) takes one.\n */\nexport type BuiltInFormatInputType =\n | \"password\"\n | \"email\"\n | \"number\"\n | \"tel\"\n | \"url\"\n | \"search\"\n | \"file\"\n | \"color\"\n | \"date\"\n | \"datetime-local\"\n | \"month\"\n | \"week\"\n | \"time\"\n | \"range\"\n | \"checkbox\"\n | \"radio\"\n | \"hidden\"\n | \"button\"\n | \"submit\"\n | \"reset\"\n | \"image\"\n\ntype NativeInputProps = React.ComponentPropsWithoutRef<\"input\">\n\n/**\n * A free-text input: its declaration is required, and its bounds and its required state come only\n * from that declaration (ENG-005), so `maxLength`, `minLength`, `pattern`, and `required` are not\n * props.\n */\nexport type FreeTextInputProps = Omit<\n NativeInputProps,\n \"type\" | \"maxLength\" | \"minLength\" | \"pattern\" | \"required\"\n> & {\n type?: \"text\"\n inputFormat: DeclaredInputFormat\n textEntry?: never\n}\n\n/**\n * What a text box that is not free text holds: digits typed as text (a date-of-birth part, a share\n * count, a one-time code) or an identifier (an account, document, or case id). Neither takes an\n * input format; each keeps its own native `maxLength`, `pattern`, and `inputMode`, and its value is\n * checked by its own rule (a whole-input number parse, the one-segment id rule).\n */\nexport type NonFreeTextEntry = \"numeric\" | \"identifier\"\n\n/** A text box that is not free text: no input format. */\nexport type NonFreeTextInputProps = Omit<NativeInputProps, \"type\"> & {\n type?: \"text\"\n textEntry: NonFreeTextEntry\n inputFormat?: never\n}\n\n/** An input whose value the browser formats: no input format. */\nexport type BuiltInFormatInputProps = Omit<NativeInputProps, \"type\"> & {\n type: BuiltInFormatInputType\n inputFormat?: never\n textEntry?: never\n}\n\nexport type InputProps = FreeTextInputProps | NonFreeTextInputProps | BuiltInFormatInputProps\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n // `textEntry` only selects the prop shape; it never reaches the DOM.\n ({ className, type, inputFormat, textEntry: _textEntry, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-10 w-full rounded-md border-2 border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n className\n )}\n ref={ref}\n {...props}\n {...(inputFormat ? formatControlAttributes(inputFormat, props) : {})}\n />\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n"]}
@@ -7,8 +7,13 @@ export interface SearchDropdownProps<T> {
7
7
  results: T[];
8
8
  /** Loading state */
9
9
  isLoading: boolean;
10
- /** Error message */
11
- error: string | null;
10
+ /** The search's failure exactly as the data layer reported it; `null` or `undefined` when it did not fail. */
11
+ error: unknown;
12
+ /**
13
+ * Renders a failed search inside the open dropdown. The consumer owns the failure's words and any
14
+ * retry, so the dropdown never shows a failure state of its own.
15
+ */
16
+ renderError: (error: unknown) => React.ReactNode;
12
17
  /** Called when a result is selected */
13
18
  onSelect: (result: T) => void;
14
19
  /** Called when search is cleared (after the value has been reset). Optional notification hook. */
@@ -23,16 +28,29 @@ export interface SearchDropdownProps<T> {
23
28
  disabled?: boolean;
24
29
  /** Icon to show in input (default: Search) */
25
30
  icon?: React.ReactNode;
26
- /** Minimum characters before showing results (default: 3) */
27
- minChars?: number;
31
+ /**
32
+ * The search rule of the hook behind the dropdown, passed from that hook rather than restated:
33
+ * `isSearchable(value)` decides whether the value is searched (and the dropdown opens), and
34
+ * `minChars` is the minimum the below-minimum hint names.
35
+ */
36
+ minChars: number;
37
+ isSearchable: (value: string) => boolean;
28
38
  /** Message to show when no results found */
29
39
  emptyMessage?: string;
40
+ /** What assistive technology hears when results arrive (default: "1 result" / "N results"). */
41
+ resultsAnnouncement?: (count: number) => string;
30
42
  /** Custom render function for each result */
31
43
  renderResult: (result: T, index: number) => React.ReactNode;
32
44
  }
33
45
  /**
34
- * Generic search dropdown component with debounced input and keyboard navigation.
35
- * Supports any data type and custom rendering.
46
+ * A search box with a results dropdown, following the combobox pattern: the input keeps focus, the
47
+ * results are a listbox, and the arrow-highlighted result is the input's active descendant. Every
48
+ * instance carries its own ids, so several can share a page.
49
+ *
50
+ * Once the value is long enough the dropdown stays open through every settled state — results, an
51
+ * empty answer, or a failure — until the user dismisses it (Escape, a click outside, a selection).
52
+ * A dismissal holds for the value it was made on: re-rendered props never reopen it, while typing,
53
+ * a new value, or ArrowDown does.
36
54
  *
37
55
  * @example
38
56
  * ```tsx
@@ -42,8 +60,10 @@ export interface SearchDropdownProps<T> {
42
60
  * results={users}
43
61
  * isLoading={isLoading}
44
62
  * error={error}
63
+ * renderError={(error) => <MyErrorState error={error} />}
64
+ * minChars={SEARCH_MIN_LENGTH}
65
+ * isSearchable={isSearchableText}
45
66
  * onSelect={(user) => console.log(user)}
46
- * onClear={() => setSearchValue('')}
47
67
  * placeholder="Search users..."
48
68
  * renderResult={(user) => (
49
69
  * <div>{user.displayName}</div>
@@ -51,5 +71,5 @@ export interface SearchDropdownProps<T> {
51
71
  * />
52
72
  * ```
53
73
  */
54
- export declare function SearchDropdown<T>({ value, onValueChange, results, isLoading, error, onSelect, onClear, placeholder, label, className, disabled, icon, minChars, emptyMessage, renderResult, }: SearchDropdownProps<T>): import("react").JSX.Element;
74
+ export declare function SearchDropdown<T>({ value, onValueChange, results, isLoading, error, renderError, onSelect, onClear, placeholder, label, className, disabled, icon, minChars, isSearchable, emptyMessage, resultsAnnouncement, renderResult, }: SearchDropdownProps<T>): import("react").JSX.Element;
55
75
  //# sourceMappingURL=search-dropdown.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"search-dropdown.d.ts","sourceRoot":"","sources":["../../../src/react/components/search-dropdown.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,2BAA2B;IAC3B,OAAO,EAAE,CAAC,EAAE,CAAC;IACb,oBAAoB;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,uCAAuC;IACvC,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,kGAAkG;IAClG,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,YAAY,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAC7D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,EAChC,KAAK,EACL,aAAa,EACb,OAAO,EACP,SAAS,EACT,KAAK,EACL,QAAQ,EACR,OAAO,EACP,WAAyB,EACzB,KAAK,EACL,SAAS,EACT,QAAgB,EAChB,IAAqC,EACrC,QAAY,EACZ,YAAiC,EACjC,YAAY,GACb,EAAE,mBAAmB,CAAC,CAAC,CAAC,+BAyJxB"}
1
+ {"version":3,"file":"search-dropdown.d.ts","sourceRoot":"","sources":["../../../src/react/components/search-dropdown.tsx"],"names":[],"mappings":"AAaA,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,2BAA2B;IAC3B,OAAO,EAAE,CAAC,EAAE,CAAC;IACb,oBAAoB;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,8GAA8G;IAC9G,KAAK,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IACjD,uCAAuC;IACvC,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,kGAAkG;IAClG,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+FAA+F;IAC/F,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,6CAA6C;IAC7C,YAAY,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAC7D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,EAChC,KAAK,EACL,aAAa,EACb,OAAO,EACP,SAAS,EACT,KAAK,EACL,WAAW,EACX,QAAQ,EACR,OAAO,EACP,WAAyB,EACzB,KAAK,EACL,SAAS,EACT,QAAgB,EAChB,IAAqC,EACrC,QAAQ,EACR,YAAY,EACZ,YAAiC,EACjC,mBAAgD,EAChD,YAAY,GACb,EAAE,mBAAmB,CAAC,CAAC,CAAC,+BAkMxB"}
@@ -1,14 +1,23 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useRef, useEffect } from 'react';
3
+ import { useState, useRef, useEffect, useId } from 'react';
4
4
  import { Input } from './input.js';
5
5
  import { Label } from './label.js';
6
6
  import { X, Search } from 'lucide-react';
7
7
  import { Spinner } from './spinner.js';
8
8
  import { cn } from "../../lib/utils.js";
9
+ function defaultResultsAnnouncement(count) {
10
+ return count === 1 ? '1 result' : `${count} results`;
11
+ }
9
12
  /**
10
- * Generic search dropdown component with debounced input and keyboard navigation.
11
- * Supports any data type and custom rendering.
13
+ * A search box with a results dropdown, following the combobox pattern: the input keeps focus, the
14
+ * results are a listbox, and the arrow-highlighted result is the input's active descendant. Every
15
+ * instance carries its own ids, so several can share a page.
16
+ *
17
+ * Once the value is long enough the dropdown stays open through every settled state — results, an
18
+ * empty answer, or a failure — until the user dismisses it (Escape, a click outside, a selection).
19
+ * A dismissal holds for the value it was made on: re-rendered props never reopen it, while typing,
20
+ * a new value, or ArrowDown does.
12
21
  *
13
22
  * @example
14
23
  * ```tsx
@@ -18,8 +27,10 @@ import { cn } from "../../lib/utils.js";
18
27
  * results={users}
19
28
  * isLoading={isLoading}
20
29
  * error={error}
30
+ * renderError={(error) => <MyErrorState error={error} />}
31
+ * minChars={SEARCH_MIN_LENGTH}
32
+ * isSearchable={isSearchableText}
21
33
  * onSelect={(user) => console.log(user)}
22
- * onClear={() => setSearchValue('')}
23
34
  * placeholder="Search users..."
24
35
  * renderResult={(user) => (
25
36
  * <div>{user.displayName}</div>
@@ -27,68 +38,96 @@ import { cn } from "../../lib/utils.js";
27
38
  * />
28
39
  * ```
29
40
  */
30
- export function SearchDropdown({ value, onValueChange, results, isLoading, error, onSelect, onClear, placeholder = 'Search...', label, className, disabled = false, icon = _jsx(Search, { className: "h-4 w-4" }), minChars = 3, emptyMessage = 'No results found', renderResult, }) {
31
- const [isOpen, setIsOpen] = useState(false);
32
- const [selectedIndex, setSelectedIndex] = useState(-1);
41
+ export function SearchDropdown({ value, onValueChange, results, isLoading, error, renderError, onSelect, onClear, placeholder = 'Search...', label, className, disabled = false, icon = _jsx(Search, { className: "h-4 w-4" }), minChars, isSearchable, emptyMessage = 'No results found', resultsAnnouncement = defaultResultsAnnouncement, renderResult, }) {
42
+ const baseId = useId();
43
+ const inputId = `${baseId}-input`;
44
+ const labelId = `${baseId}-label`;
45
+ const listboxId = `${baseId}-listbox`;
46
+ const panelId = `${baseId}-panel`;
47
+ const hintId = `${baseId}-hint`;
48
+ const optionId = (index) => `${baseId}-option-${index}`;
49
+ const [dismissedFor, setDismissedFor] = useState(null);
50
+ // The highlight belongs to the value it was made on, so a new query starts with none, while a
51
+ // refreshed result list for the same query keeps it.
52
+ const [highlight, setHighlight] = useState(null);
33
53
  const containerRef = useRef(null);
34
54
  const inputRef = useRef(null);
55
+ const searchable = isSearchable(value);
56
+ const isOpen = searchable && dismissedFor !== value;
57
+ const failed = error !== null && error !== undefined;
58
+ const showOptions = isOpen && !failed && !isLoading && results.length > 0;
59
+ const activeIndex = showOptions && highlight && highlight.value === value && highlight.index < results.length ? highlight.index : -1;
60
+ const showHint = value.length > 0 && !searchable;
61
+ const announcement = !isOpen || failed
62
+ ? ''
63
+ : isLoading
64
+ ? 'Searching...'
65
+ : results.length === 0
66
+ ? emptyMessage
67
+ : resultsAnnouncement(results.length);
35
68
  useEffect(() => {
69
+ if (!isOpen)
70
+ return;
36
71
  const handleClickOutside = (event) => {
37
72
  if (containerRef.current && !containerRef.current.contains(event.target)) {
38
- setIsOpen(false);
73
+ setDismissedFor(value);
39
74
  }
40
75
  };
41
76
  document.addEventListener('mousedown', handleClickOutside);
42
77
  return () => document.removeEventListener('mousedown', handleClickOutside);
43
- }, []);
44
- useEffect(() => {
45
- if (value.length >= minChars && (results.length > 0 || isLoading || error)) {
46
- setIsOpen(true);
47
- }
48
- else {
49
- setIsOpen(false);
50
- }
51
- }, [value, results, isLoading, error, minChars]);
52
- useEffect(() => {
53
- setSelectedIndex(-1);
54
- }, [results]);
78
+ }, [isOpen, value]);
79
+ const select = (result) => {
80
+ onSelect(result);
81
+ setDismissedFor(value);
82
+ };
55
83
  const handleKeyDown = (e) => {
56
- if (!isOpen || results.length === 0)
57
- return;
58
84
  switch (e.key) {
59
85
  case 'ArrowDown':
86
+ if (!searchable)
87
+ return;
60
88
  e.preventDefault();
61
- setSelectedIndex((prev) => (prev < results.length - 1 ? prev + 1 : prev));
89
+ if (!isOpen) {
90
+ setDismissedFor(null);
91
+ return;
92
+ }
93
+ if (!showOptions)
94
+ return;
95
+ setHighlight({ value, index: activeIndex < results.length - 1 ? activeIndex + 1 : activeIndex });
62
96
  break;
63
97
  case 'ArrowUp':
98
+ if (!showOptions)
99
+ return;
64
100
  e.preventDefault();
65
- setSelectedIndex((prev) => (prev > 0 ? prev - 1 : 0));
101
+ setHighlight({ value, index: activeIndex > 0 ? activeIndex - 1 : 0 });
66
102
  break;
67
103
  case 'Enter':
104
+ if (activeIndex < 0)
105
+ return;
68
106
  e.preventDefault();
69
- if (selectedIndex >= 0 && selectedIndex < results.length) {
70
- onSelect(results[selectedIndex]);
71
- setIsOpen(false);
72
- }
107
+ select(results[activeIndex]);
73
108
  break;
74
109
  case 'Escape':
110
+ // Escape never falls through to the search field's own clear, which would skip onClear:
111
+ // it closes an open dropdown, and clears a closed one through handleClear.
75
112
  e.preventDefault();
76
- setIsOpen(false);
77
- inputRef.current?.blur();
113
+ if (isOpen)
114
+ setDismissedFor(value);
115
+ else if (value)
116
+ handleClear();
78
117
  break;
79
118
  }
80
119
  };
81
- const handleResultClick = (result) => {
82
- onSelect(result);
83
- setIsOpen(false);
84
- };
85
120
  const handleClear = () => {
86
121
  onValueChange('');
87
122
  onClear?.();
88
- setIsOpen(false);
123
+ setDismissedFor(null);
89
124
  inputRef.current?.focus();
90
125
  };
91
- const showDropdown = isOpen && value.length >= minChars;
92
- return (_jsxs("div", { ref: containerRef, className: cn('relative', className), children: [label && (_jsx(Label, { htmlFor: "search-input", className: "mb-2 block", children: label })), _jsxs("div", { className: "relative", children: [_jsx("div", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground", children: icon }), _jsx(Input, { ref: inputRef, id: "search-input", type: "text", value: value, onChange: (e) => onValueChange(e.target.value), onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, className: "pl-10 pr-10" }), _jsx("div", { className: "absolute right-3 top-1/2 -translate-y-1/2", children: isLoading ? (_jsx(Spinner, { size: "xs", className: "text-muted-foreground" })) : value ? (_jsx("button", { type: "button", onClick: handleClear, className: "text-muted-foreground hover:text-foreground transition-colors", "aria-label": "Clear search", children: _jsx(X, { className: "h-4 w-4" }) })) : null })] }), showDropdown && (_jsx("div", { className: "absolute z-50 w-full mt-1 bg-popover border rounded-md elevation-raised max-h-60 overflow-auto", children: error ? (_jsx("div", { className: "px-3 py-2 text-sm text-destructive", children: error })) : isLoading ? (_jsxs("div", { className: "px-3 py-2 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Spinner, { size: "xs" }), "Searching..."] })) : results.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: emptyMessage })) : (results.map((result, index) => (_jsx("button", { type: "button", onClick: () => handleResultClick(result), onMouseEnter: () => setSelectedIndex(index), className: cn('w-full text-left transition-colors cursor-pointer', 'hover:bg-accent focus:bg-accent focus:outline-hidden', selectedIndex === index && 'bg-accent'), children: renderResult(result, index) }, index)))) })), value.length > 0 && value.length < minChars && (_jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: ["Type at least ", minChars, " characters to search"] }))] }));
126
+ return (_jsxs("div", { ref: containerRef, className: cn('relative', className), children: [label && (_jsx(Label, { id: labelId, htmlFor: inputId, className: "mb-2 block", children: label })), _jsxs("div", { className: "relative", children: [_jsx("div", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground", "aria-hidden": "true", children: icon }), _jsx(Input, { ref: inputRef, id: inputId, type: "search", role: "combobox", "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-controls": isOpen ? (showOptions ? listboxId : panelId) : undefined, "aria-activedescendant": activeIndex >= 0 ? optionId(activeIndex) : undefined, "aria-describedby": showHint ? hintId : undefined, autoComplete: "off", value: value, onChange: (e) => {
127
+ setDismissedFor(null);
128
+ onValueChange(e.target.value);
129
+ }, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, className: "pl-10 pr-10 [&::-webkit-search-cancel-button]:appearance-none" }), _jsx("div", { className: "absolute right-3 top-1/2 -translate-y-1/2", children: isLoading ? (_jsx(Spinner, { size: "xs", className: "text-muted-foreground" })) : value ? (_jsx("button", { type: "button", onClick: handleClear, className: "text-muted-foreground hover:text-foreground transition-colors", "aria-label": "Clear search", children: _jsx(X, { className: "h-4 w-4" }) })) : null })] }), isOpen && (_jsx("div", { id: panelId, className: "absolute z-50 w-full mt-1 bg-popover border rounded-md elevation-raised max-h-60 overflow-auto", children: failed ? (renderError(error)) : isLoading ? (_jsxs("div", { className: "px-3 py-2 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Spinner, { size: "xs" }), "Searching..."] })) : results.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: emptyMessage })) : (_jsx("div", { role: "listbox", id: listboxId, "aria-labelledby": label ? labelId : undefined, "aria-label": label ? undefined : placeholder, children: results.map((result, index) => (_jsx("div", { id: optionId(index), role: "option", "aria-selected": index === activeIndex,
130
+ // Keeps focus in the input, where the combobox's keyboard handling lives.
131
+ onMouseDown: (e) => e.preventDefault(), onClick: () => select(result), onMouseEnter: () => setHighlight({ value, index }), className: cn('w-full text-left transition-colors cursor-pointer hover:bg-accent', index === activeIndex && 'bg-accent'), children: renderResult(result, index) }, index))) })) })), _jsx("div", { role: "status", "aria-live": "polite", className: "sr-only", children: announcement }), showHint && (_jsxs("p", { id: hintId, className: "text-xs text-muted-foreground mt-1", children: ["Type at least ", minChars, " characters to search"] }))] }));
93
132
  }
94
133
  //# sourceMappingURL=search-dropdown.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"search-dropdown.js","sourceRoot":"","sources":["../../../src/react/components/search-dropdown.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useRef, useEffect } from 'react';\nimport { Input } from './input.js';\nimport { Label } from './label.js';\nimport { X, Search } from 'lucide-react';\nimport { Spinner } from './spinner.js';\nimport { cn } from \"../../lib/utils.js\";\n\nexport interface SearchDropdownProps<T> {\n /** Current search value */\n value: string;\n /** Called when search value changes */\n onValueChange: (value: string) => void;\n /** Search results array */\n results: T[];\n /** Loading state */\n isLoading: boolean;\n /** Error message */\n error: string | null;\n /** Called when a result is selected */\n onSelect: (result: T) => void;\n /** Called when search is cleared (after the value has been reset). Optional notification hook. */\n onClear?: () => void;\n /** Placeholder text */\n placeholder?: string;\n /** Label for the input */\n label?: string;\n /** Custom className */\n className?: string;\n /** Disabled state */\n disabled?: boolean;\n /** Icon to show in input (default: Search) */\n icon?: React.ReactNode;\n /** Minimum characters before showing results (default: 3) */\n minChars?: number;\n /** Message to show when no results found */\n emptyMessage?: string;\n /** Custom render function for each result */\n renderResult: (result: T, index: number) => React.ReactNode;\n}\n\n/**\n * Generic search dropdown component with debounced input and keyboard navigation.\n * Supports any data type and custom rendering.\n * \n * @example\n * ```tsx\n * <SearchDropdown<User>\n * value={searchValue}\n * onValueChange={setSearchValue}\n * results={users}\n * isLoading={isLoading}\n * error={error}\n * onSelect={(user) => console.log(user)}\n * onClear={() => setSearchValue('')}\n * placeholder=\"Search users...\"\n * renderResult={(user) => (\n * <div>{user.displayName}</div>\n * )}\n * />\n * ```\n */\nexport function SearchDropdown<T>({\n value,\n onValueChange,\n results,\n isLoading,\n error,\n onSelect,\n onClear,\n placeholder = 'Search...',\n label,\n className,\n disabled = false,\n icon = <Search className=\"h-4 w-4\" />,\n minChars = 3,\n emptyMessage = 'No results found',\n renderResult,\n}: SearchDropdownProps<T>) {\n const [isOpen, setIsOpen] = useState(false);\n const [selectedIndex, setSelectedIndex] = useState(-1);\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, []);\n\n useEffect(() => {\n if (value.length >= minChars && (results.length > 0 || isLoading || error)) {\n setIsOpen(true);\n } else {\n setIsOpen(false);\n }\n }, [value, results, isLoading, error, minChars]);\n\n useEffect(() => {\n setSelectedIndex(-1);\n }, [results]);\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (!isOpen || results.length === 0) return;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n setSelectedIndex((prev) => (prev < results.length - 1 ? prev + 1 : prev));\n break;\n case 'ArrowUp':\n e.preventDefault();\n setSelectedIndex((prev) => (prev > 0 ? prev - 1 : 0));\n break;\n case 'Enter':\n e.preventDefault();\n if (selectedIndex >= 0 && selectedIndex < results.length) {\n onSelect(results[selectedIndex]);\n setIsOpen(false);\n }\n break;\n case 'Escape':\n e.preventDefault();\n setIsOpen(false);\n inputRef.current?.blur();\n break;\n }\n };\n\n const handleResultClick = (result: T) => {\n onSelect(result);\n setIsOpen(false);\n };\n\n const handleClear = () => {\n onValueChange('');\n onClear?.();\n setIsOpen(false);\n inputRef.current?.focus();\n };\n\n const showDropdown = isOpen && value.length >= minChars;\n\n return (\n <div ref={containerRef} className={cn('relative', className)}>\n {label && (\n <Label htmlFor=\"search-input\" className=\"mb-2 block\">\n {label}\n </Label>\n )}\n \n <div className=\"relative\">\n <div className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\">\n {icon}\n </div>\n \n <Input\n ref={inputRef}\n id=\"search-input\"\n type=\"text\"\n value={value}\n onChange={(e) => onValueChange(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled}\n className=\"pl-10 pr-10\"\n />\n\n <div className=\"absolute right-3 top-1/2 -translate-y-1/2\">\n {isLoading ? (\n <Spinner size=\"xs\" className=\"text-muted-foreground\" />\n ) : value ? (\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Clear search\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n ) : null}\n </div>\n </div>\n\n {showDropdown && (\n <div className=\"absolute z-50 w-full mt-1 bg-popover border rounded-md elevation-raised max-h-60 overflow-auto\">\n {error ? (\n <div className=\"px-3 py-2 text-sm text-destructive\">\n {error}\n </div>\n ) : isLoading ? (\n <div className=\"px-3 py-2 text-sm text-muted-foreground flex items-center gap-2\">\n <Spinner size=\"xs\" />\n Searching...\n </div>\n ) : results.length === 0 ? (\n <div className=\"px-3 py-2 text-sm text-muted-foreground\">\n {emptyMessage}\n </div>\n ) : (\n results.map((result, index) => (\n <button\n key={index}\n type=\"button\"\n onClick={() => handleResultClick(result)}\n onMouseEnter={() => setSelectedIndex(index)}\n className={cn(\n 'w-full text-left transition-colors cursor-pointer',\n 'hover:bg-accent focus:bg-accent focus:outline-hidden',\n selectedIndex === index && 'bg-accent'\n )}\n >\n {renderResult(result, index)}\n </button>\n ))\n )}\n </div>\n )}\n \n {value.length > 0 && value.length < minChars && (\n <p className=\"text-xs text-muted-foreground mt-1\">\n Type at least {minChars} characters to search\n </p>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"search-dropdown.js","sourceRoot":"","sources":["../../../src/react/components/search-dropdown.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useRef, useEffect, useId } from 'react';\nimport { Input } from './input.js';\nimport { Label } from './label.js';\nimport { X, Search } from 'lucide-react';\nimport { Spinner } from './spinner.js';\nimport { cn } from \"../../lib/utils.js\";\n\nfunction defaultResultsAnnouncement(count: number): string {\n return count === 1 ? '1 result' : `${count} results`;\n}\n\nexport interface SearchDropdownProps<T> {\n /** Current search value */\n value: string;\n /** Called when search value changes */\n onValueChange: (value: string) => void;\n /** Search results array */\n results: T[];\n /** Loading state */\n isLoading: boolean;\n /** The search's failure exactly as the data layer reported it; `null` or `undefined` when it did not fail. */\n error: unknown;\n /**\n * Renders a failed search inside the open dropdown. The consumer owns the failure's words and any\n * retry, so the dropdown never shows a failure state of its own.\n */\n renderError: (error: unknown) => React.ReactNode;\n /** Called when a result is selected */\n onSelect: (result: T) => void;\n /** Called when search is cleared (after the value has been reset). Optional notification hook. */\n onClear?: () => void;\n /** Placeholder text */\n placeholder?: string;\n /** Label for the input */\n label?: string;\n /** Custom className */\n className?: string;\n /** Disabled state */\n disabled?: boolean;\n /** Icon to show in input (default: Search) */\n icon?: React.ReactNode;\n /**\n * The search rule of the hook behind the dropdown, passed from that hook rather than restated:\n * `isSearchable(value)` decides whether the value is searched (and the dropdown opens), and\n * `minChars` is the minimum the below-minimum hint names.\n */\n minChars: number;\n isSearchable: (value: string) => boolean;\n /** Message to show when no results found */\n emptyMessage?: string;\n /** What assistive technology hears when results arrive (default: \"1 result\" / \"N results\"). */\n resultsAnnouncement?: (count: number) => string;\n /** Custom render function for each result */\n renderResult: (result: T, index: number) => React.ReactNode;\n}\n\n/**\n * A search box with a results dropdown, following the combobox pattern: the input keeps focus, the\n * results are a listbox, and the arrow-highlighted result is the input's active descendant. Every\n * instance carries its own ids, so several can share a page.\n *\n * Once the value is long enough the dropdown stays open through every settled state — results, an\n * empty answer, or a failure — until the user dismisses it (Escape, a click outside, a selection).\n * A dismissal holds for the value it was made on: re-rendered props never reopen it, while typing,\n * a new value, or ArrowDown does.\n *\n * @example\n * ```tsx\n * <SearchDropdown<User>\n * value={searchValue}\n * onValueChange={setSearchValue}\n * results={users}\n * isLoading={isLoading}\n * error={error}\n * renderError={(error) => <MyErrorState error={error} />}\n * minChars={SEARCH_MIN_LENGTH}\n * isSearchable={isSearchableText}\n * onSelect={(user) => console.log(user)}\n * placeholder=\"Search users...\"\n * renderResult={(user) => (\n * <div>{user.displayName}</div>\n * )}\n * />\n * ```\n */\nexport function SearchDropdown<T>({\n value,\n onValueChange,\n results,\n isLoading,\n error,\n renderError,\n onSelect,\n onClear,\n placeholder = 'Search...',\n label,\n className,\n disabled = false,\n icon = <Search className=\"h-4 w-4\" />,\n minChars,\n isSearchable,\n emptyMessage = 'No results found',\n resultsAnnouncement = defaultResultsAnnouncement,\n renderResult,\n}: SearchDropdownProps<T>) {\n const baseId = useId();\n const inputId = `${baseId}-input`;\n const labelId = `${baseId}-label`;\n const listboxId = `${baseId}-listbox`;\n const panelId = `${baseId}-panel`;\n const hintId = `${baseId}-hint`;\n const optionId = (index: number) => `${baseId}-option-${index}`;\n\n const [dismissedFor, setDismissedFor] = useState<string | null>(null);\n // The highlight belongs to the value it was made on, so a new query starts with none, while a\n // refreshed result list for the same query keeps it.\n const [highlight, setHighlight] = useState<{ value: string; index: number } | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const searchable = isSearchable(value);\n const isOpen = searchable && dismissedFor !== value;\n const failed = error !== null && error !== undefined;\n const showOptions = isOpen && !failed && !isLoading && results.length > 0;\n const activeIndex =\n showOptions && highlight && highlight.value === value && highlight.index < results.length ? highlight.index : -1;\n const showHint = value.length > 0 && !searchable;\n const announcement =\n !isOpen || failed\n ? ''\n : isLoading\n ? 'Searching...'\n : results.length === 0\n ? emptyMessage\n : resultsAnnouncement(results.length);\n\n useEffect(() => {\n if (!isOpen) return;\n const handleClickOutside = (event: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\n setDismissedFor(value);\n }\n };\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, [isOpen, value]);\n\n const select = (result: T) => {\n onSelect(result);\n setDismissedFor(value);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n switch (e.key) {\n case 'ArrowDown':\n if (!searchable) return;\n e.preventDefault();\n if (!isOpen) {\n setDismissedFor(null);\n return;\n }\n if (!showOptions) return;\n setHighlight({ value, index: activeIndex < results.length - 1 ? activeIndex + 1 : activeIndex });\n break;\n case 'ArrowUp':\n if (!showOptions) return;\n e.preventDefault();\n setHighlight({ value, index: activeIndex > 0 ? activeIndex - 1 : 0 });\n break;\n case 'Enter':\n if (activeIndex < 0) return;\n e.preventDefault();\n select(results[activeIndex]);\n break;\n case 'Escape':\n // Escape never falls through to the search field's own clear, which would skip onClear:\n // it closes an open dropdown, and clears a closed one through handleClear.\n e.preventDefault();\n if (isOpen) setDismissedFor(value);\n else if (value) handleClear();\n break;\n }\n };\n\n const handleClear = () => {\n onValueChange('');\n onClear?.();\n setDismissedFor(null);\n inputRef.current?.focus();\n };\n\n return (\n <div ref={containerRef} className={cn('relative', className)}>\n {label && (\n <Label id={labelId} htmlFor={inputId} className=\"mb-2 block\">\n {label}\n </Label>\n )}\n\n <div className=\"relative\">\n <div className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\" aria-hidden=\"true\">\n {icon}\n </div>\n\n <Input\n ref={inputRef}\n id={inputId}\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? (showOptions ? listboxId : panelId) : undefined}\n aria-activedescendant={activeIndex >= 0 ? optionId(activeIndex) : undefined}\n aria-describedby={showHint ? hintId : undefined}\n autoComplete=\"off\"\n value={value}\n onChange={(e) => {\n setDismissedFor(null);\n onValueChange(e.target.value);\n }}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled}\n className=\"pl-10 pr-10 [&::-webkit-search-cancel-button]:appearance-none\"\n />\n\n <div className=\"absolute right-3 top-1/2 -translate-y-1/2\">\n {isLoading ? (\n <Spinner size=\"xs\" className=\"text-muted-foreground\" />\n ) : value ? (\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Clear search\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n ) : null}\n </div>\n </div>\n\n {isOpen && (\n <div id={panelId} className=\"absolute z-50 w-full mt-1 bg-popover border rounded-md elevation-raised max-h-60 overflow-auto\">\n {failed ? (\n renderError(error)\n ) : isLoading ? (\n <div className=\"px-3 py-2 text-sm text-muted-foreground flex items-center gap-2\">\n <Spinner size=\"xs\" />\n Searching...\n </div>\n ) : results.length === 0 ? (\n <div className=\"px-3 py-2 text-sm text-muted-foreground\">\n {emptyMessage}\n </div>\n ) : (\n <div\n role=\"listbox\"\n id={listboxId}\n aria-labelledby={label ? labelId : undefined}\n aria-label={label ? undefined : placeholder}\n >\n {results.map((result, index) => (\n <div\n key={index}\n id={optionId(index)}\n role=\"option\"\n aria-selected={index === activeIndex}\n // Keeps focus in the input, where the combobox's keyboard handling lives.\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => select(result)}\n onMouseEnter={() => setHighlight({ value, index })}\n className={cn(\n 'w-full text-left transition-colors cursor-pointer hover:bg-accent',\n index === activeIndex && 'bg-accent'\n )}\n >\n {renderResult(result, index)}\n </div>\n ))}\n </div>\n )}\n </div>\n )}\n\n {/* Always mounted, so every change of the open panel's state is announced. A failure is\n announced by the consumer's renderError. */}\n <div role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </div>\n\n {showHint && (\n <p id={hintId} className=\"text-xs text-muted-foreground mt-1\">\n Type at least {minChars} characters to search\n </p>\n )}\n </div>\n );\n}\n"]}
@@ -1,4 +1,27 @@
1
1
  import * as React from "react";
2
- declare const Textarea: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>, "ref"> & React.RefAttributes<HTMLTextAreaElement>>;
2
+ import type { DeclaredInputFormat } from "@ttt-productions/input-format-core";
3
+ type NativeTextareaProps = React.ComponentPropsWithoutRef<"textarea">;
4
+ /**
5
+ * A multi-line free-text input: its declaration is required, and its length bounds come only from
6
+ * that declaration (ENG-005), so `maxLength`, `minLength`, and `required` are not props.
7
+ */
8
+ export type FreeTextTextareaProps = Omit<NativeTextareaProps, "maxLength" | "minLength" | "required"> & {
9
+ inputFormat: DeclaredInputFormat;
10
+ textEntry?: never;
11
+ };
12
+ /**
13
+ * What a multi-line box that is not free text holds: identifiers (account, document, or case ids),
14
+ * or a list of items. The box's text is split into items by the consumer, and each item is judged
15
+ * by its own rule (the one-segment id rule, or the item's own field declaration) — so the box has
16
+ * no input format of its own.
17
+ */
18
+ export type NonFreeTextareaEntry = "identifier" | "list";
19
+ /** A multi-line box that is not free text: no input format; native bounds stay the caller's. */
20
+ export type NonFreeTextTextareaProps = NativeTextareaProps & {
21
+ textEntry: NonFreeTextareaEntry;
22
+ inputFormat?: never;
23
+ };
24
+ export type TextareaProps = FreeTextTextareaProps | NonFreeTextTextareaProps;
25
+ declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
3
26
  export { Textarea };
4
27
  //# sourceMappingURL=textarea.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../src/react/components/textarea.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,QAAA,MAAM,QAAQ,0LAab,CAAA;AAGD,OAAO,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../src/react/components/textarea.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA;AAI7E,KAAK,mBAAmB,GAAG,KAAK,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAA;AAErE;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,mBAAmB,EAAE,WAAW,GAAG,WAAW,GAAG,UAAU,CAAC,GAAG;IACtG,WAAW,EAAE,mBAAmB,CAAA;IAChC,SAAS,CAAC,EAAE,KAAK,CAAA;CAClB,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,MAAM,CAAA;AAExD,gGAAgG;AAChG,MAAM,MAAM,wBAAwB,GAAG,mBAAmB,GAAG;IAC3D,SAAS,EAAE,oBAAoB,CAAA;IAC/B,WAAW,CAAC,EAAE,KAAK,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,qBAAqB,GAAG,wBAAwB,CAAA;AAE5E,QAAA,MAAM,QAAQ,2FAeb,CAAA;AAGD,OAAO,EAAE,QAAQ,EAAE,CAAA"}
@@ -2,8 +2,11 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "../../lib/utils.js";
5
- const Textarea = React.forwardRef(({ className, ...props }, ref) => {
6
- return (_jsx("textarea", { className: cn("flex min-h-[80px] w-full rounded-md border-2 border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className), ref: ref, ...props }));
5
+ import { formatControlAttributes } from "./format-control.js";
6
+ const Textarea = React.forwardRef(
7
+ // `textEntry` only selects the prop shape; it never reaches the DOM.
8
+ ({ className, inputFormat, textEntry: _textEntry, ...props }, ref) => {
9
+ return (_jsx("textarea", { className: cn("flex min-h-[80px] w-full rounded-md border-2 border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className), ref: ref, ...props, ...(inputFormat ? formatControlAttributes(inputFormat, props) : {}) }));
7
10
  });
8
11
  Textarea.displayName = "Textarea";
9
12
  export { Textarea };
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.js","sourceRoot":"","sources":["../../../src/react/components/textarea.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAEvC,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAC/B,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE;IAC/B,OAAO,CACL,mBACE,SAAS,EAAE,EAAE,CACX,uTAAuT,EACvT,SAAS,CACV,EACD,GAAG,EAAE,GAAG,KACJ,KAAK,GACT,CACH,CAAA;AACH,CAAC,CACF,CAAA;AACD,QAAQ,CAAC,WAAW,GAAG,UAAU,CAAA;AAEjC,OAAO,EAAE,QAAQ,EAAE,CAAA","sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"../../lib/utils.js\"\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<\"textarea\">>(\n ({ className, ...props }, ref) => {\n return (\n <textarea\n className={cn(\n \"flex min-h-[80px] w-full rounded-md border-2 border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n className\n )}\n ref={ref}\n {...props}\n />\n )\n }\n)\nTextarea.displayName = \"Textarea\"\n\nexport { Textarea }\n"]}
1
+ {"version":3,"file":"textarea.js","sourceRoot":"","sources":["../../../src/react/components/textarea.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AA6B7D,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU;AAC/B,qEAAqE;AACrE,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE;IACnE,OAAO,CACL,mBACE,SAAS,EAAE,EAAE,CACX,uTAAuT,EACvT,SAAS,CACV,EACD,GAAG,EAAE,GAAG,KACJ,KAAK,KACL,CAAC,WAAW,CAAC,CAAC,CAAC,uBAAuB,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GACpE,CACH,CAAA;AACH,CAAC,CACF,CAAA;AACD,QAAQ,CAAC,WAAW,GAAG,UAAU,CAAA;AAEjC,OAAO,EAAE,QAAQ,EAAE,CAAA","sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport type { DeclaredInputFormat } from \"@ttt-productions/input-format-core\"\nimport { cn } from \"../../lib/utils.js\"\nimport { formatControlAttributes } from \"./format-control.js\"\n\ntype NativeTextareaProps = React.ComponentPropsWithoutRef<\"textarea\">\n\n/**\n * A multi-line free-text input: its declaration is required, and its length bounds come only from\n * that declaration (ENG-005), so `maxLength`, `minLength`, and `required` are not props.\n */\nexport type FreeTextTextareaProps = Omit<NativeTextareaProps, \"maxLength\" | \"minLength\" | \"required\"> & {\n inputFormat: DeclaredInputFormat\n textEntry?: never\n}\n\n/**\n * What a multi-line box that is not free text holds: identifiers (account, document, or case ids),\n * or a list of items. The box's text is split into items by the consumer, and each item is judged\n * by its own rule (the one-segment id rule, or the item's own field declaration) — so the box has\n * no input format of its own.\n */\nexport type NonFreeTextareaEntry = \"identifier\" | \"list\"\n\n/** A multi-line box that is not free text: no input format; native bounds stay the caller's. */\nexport type NonFreeTextTextareaProps = NativeTextareaProps & {\n textEntry: NonFreeTextareaEntry\n inputFormat?: never\n}\n\nexport type TextareaProps = FreeTextTextareaProps | NonFreeTextTextareaProps\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n // `textEntry` only selects the prop shape; it never reaches the DOM.\n ({ className, inputFormat, textEntry: _textEntry, ...props }, ref) => {\n return (\n <textarea\n className={cn(\n \"flex min-h-[80px] w-full rounded-md border-2 border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n className\n )}\n ref={ref}\n {...props}\n {...(inputFormat ? formatControlAttributes(inputFormat, props) : {})}\n />\n )\n }\n)\nTextarea.displayName = \"Textarea\"\n\nexport { Textarea }\n"]}
@@ -4,6 +4,7 @@ export type { SpinnerProps, SpinnerSize } from "./components/spinner.js";
4
4
  export { Spinner } from "./components/spinner.js";
5
5
  export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from "./components/card.js";
6
6
  export { Label } from "./components/label.js";
7
+ export type { TextareaProps, FreeTextTextareaProps, NonFreeTextTextareaProps, NonFreeTextareaEntry } from "./components/textarea.js";
7
8
  export { Textarea } from "./components/textarea.js";
8
9
  export { Skeleton } from "./components/skeleton.js";
9
10
  export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription } from "./components/dialog.js";
@@ -17,6 +18,7 @@ export { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, Toas
17
18
  export { Toaster } from "./components/toaster.js";
18
19
  export type { ToastVariant, ToasterToast } from "./hooks/use-toast.js";
19
20
  export { toast, useToast } from "./hooks/use-toast.js";
21
+ export type { InputProps, FreeTextInputProps, NonFreeTextInputProps, NonFreeTextEntry, BuiltInFormatInputProps, BuiltInFormatInputType } from "./components/input.js";
20
22
  export { Input } from "./components/input.js";
21
23
  export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton } from "./components/select.js";
22
24
  export { Tabs, TabsList, TabsTrigger, TabsContent } from "./components/tabs.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nexport type { ButtonProps } from \"./components/button.js\";\nexport { Button, buttonVariants } from \"./components/button.js\";\nexport type { SpinnerProps, SpinnerSize } from \"./components/spinner.js\";\nexport { Spinner } from \"./components/spinner.js\";\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from \"./components/card.js\";\nexport { Label } from \"./components/label.js\";\nexport { Textarea } from \"./components/textarea.js\";\nexport { Skeleton } from \"./components/skeleton.js\";\nexport { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription } from \"./components/dialog.js\";\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } from \"./components/popover.js\";\nexport { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup } from \"./components/dropdown-menu.js\";\nexport { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarSeparator, MenubarLabel, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarPortal, MenubarSubContent, MenubarSubTrigger, MenubarGroup, MenubarSub, MenubarShortcut } from \"./components/menubar.js\";\nexport { AlertDialog, AlertDialogPortal, AlertDialogOverlay, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel } from \"./components/alert-dialog.js\";\nexport { Alert, AlertTitle, AlertDescription } from \"./components/alert.js\";\nexport type { ToastProps, ToastActionElement } from \"./components/toast.js\";\nexport { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction } from \"./components/toast.js\";\nexport { Toaster } from \"./components/toaster.js\";\nexport type { ToastVariant, ToasterToast } from \"./hooks/use-toast.js\";\nexport { toast, useToast } from \"./hooks/use-toast.js\";\nexport { Input } from \"./components/input.js\";\nexport { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton } from \"./components/select.js\";\nexport { Tabs, TabsList, TabsTrigger, TabsContent } from \"./components/tabs.js\";\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from \"./components/tooltip.js\";\nexport { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField } from \"./components/form.js\";\nexport { Checkbox } from \"./components/checkbox.js\";\nexport { Switch } from \"./components/switch.js\";\nexport type { BadgeProps } from \"./components/badge.js\";\nexport { Badge, badgeVariants } from \"./components/badge.js\";\nexport { Avatar, AvatarImage, AvatarFallback } from \"./components/avatar.js\";\nexport { Separator } from \"./components/separator.js\";\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from \"./components/accordion.js\";\nexport { RadioGroup, RadioGroupItem } from \"./components/radio-group.js\";\nexport { Progress } from \"./components/progress.js\";\nexport { Slider } from \"./components/slider.js\";\nexport type { DatePickerProps } from \"./components/date-picker.js\";\nexport { DatePicker } from \"./components/date-picker.js\";\nexport type { DateTimePickerProps, DateTimePickerTime } from \"./components/date-time-picker.js\";\nexport { DateTimePicker } from \"./components/date-time-picker.js\";\nexport { ScrollArea, ScrollBar } from \"./components/scroll-area.js\";\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption } from \"./components/table.js\";\nexport { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription } from \"./components/sheet.js\";\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent } from \"./components/collapsible.js\";\nexport type { SearchDropdownProps } from \"./components/search-dropdown.js\";\nexport { SearchDropdown } from \"./components/search-dropdown.js\";\n\nexport type { ConsequenceDialogProps, ConsequenceDialogReason, ConsequenceDialogTypedConfirmation } from \"./components/consequence-dialog.js\";\nexport { ConsequenceDialog } from \"./components/consequence-dialog.js\";\n\nexport type { MaxWidthOption, ScreenAdaptiveViewProps } from \"./components/layout/screen-adaptive-view.js\";\nexport { ScreenAdaptiveView } from \"./components/layout/screen-adaptive-view.js\";\n\nexport { useMediaQuery } from \"./hooks/use-media-query.js\";\n\nexport { useAsyncAction } from \"./hooks/use-async-action.js\";\nexport type { AsyncAction, UseAsyncActionOptions } from \"./hooks/use-async-action.js\";\n\n// Shared building blocks\nexport { RelativeTime } from \"./components/relative-time.js\";\nexport type { RelativeTimeProps } from \"./components/relative-time.js\";\n\nexport { EndOfListIndicator } from \"./components/end-of-list-indicator.js\";\nexport type { EndOfListIndicatorProps } from \"./components/end-of-list-indicator.js\";\n\nexport { ScrollToTopButton } from \"./components/scroll-to-top-button.js\";\nexport type { ScrollToTopButtonProps } from \"./components/scroll-to-top-button.js\";\n\nexport { ChunkErrorRecovery } from \"./components/chunk-error-recovery.js\";\nexport type { ChunkErrorRecoveryProps } from \"./components/chunk-error-recovery.js\";\n\n// List pagination — the one Previous / counter / Next control plus the page-state\n// hooks for its two flavors (known total, and cursor/`hasMore`).\nexport { ListPagination } from \"./components/list-pagination.js\";\nexport type { ListPaginationProps } from \"./components/list-pagination.js\";\n\nexport { usePagedList } from \"./hooks/use-paged-list.js\";\nexport type { ListPaginationState, PagedList, UsePagedListOptions } from \"./hooks/use-paged-list.js\";\n\nexport { useCursorPage } from \"./hooks/use-cursor-page.js\";\nexport type { CursorPage, CursorPager, UseCursorPageOptions } from \"./hooks/use-cursor-page.js\";\n\n// Return-scroll: restore a list's window offset when the user comes back to it after a\n// route change (save on leave, restore once on an eligible + ready return, keyed by the\n// caller's canonical list URL).\nexport { useReturnScroll, saveReturnScroll, clearReturnScroll, readReturnScroll } from \"./hooks/use-return-scroll.js\";\nexport type { ReturnScrollEntry, UseReturnScrollOptions, UseReturnScrollResult } from \"./hooks/use-return-scroll.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nexport type { ButtonProps } from \"./components/button.js\";\nexport { Button, buttonVariants } from \"./components/button.js\";\nexport type { SpinnerProps, SpinnerSize } from \"./components/spinner.js\";\nexport { Spinner } from \"./components/spinner.js\";\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from \"./components/card.js\";\nexport { Label } from \"./components/label.js\";\nexport type { TextareaProps, FreeTextTextareaProps, NonFreeTextTextareaProps, NonFreeTextareaEntry } from \"./components/textarea.js\";\nexport { Textarea } from \"./components/textarea.js\";\nexport { Skeleton } from \"./components/skeleton.js\";\nexport { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription } from \"./components/dialog.js\";\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } from \"./components/popover.js\";\nexport { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup } from \"./components/dropdown-menu.js\";\nexport { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarSeparator, MenubarLabel, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarPortal, MenubarSubContent, MenubarSubTrigger, MenubarGroup, MenubarSub, MenubarShortcut } from \"./components/menubar.js\";\nexport { AlertDialog, AlertDialogPortal, AlertDialogOverlay, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel } from \"./components/alert-dialog.js\";\nexport { Alert, AlertTitle, AlertDescription } from \"./components/alert.js\";\nexport type { ToastProps, ToastActionElement } from \"./components/toast.js\";\nexport { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction } from \"./components/toast.js\";\nexport { Toaster } from \"./components/toaster.js\";\nexport type { ToastVariant, ToasterToast } from \"./hooks/use-toast.js\";\nexport { toast, useToast } from \"./hooks/use-toast.js\";\nexport type { InputProps, FreeTextInputProps, NonFreeTextInputProps, NonFreeTextEntry, BuiltInFormatInputProps, BuiltInFormatInputType } from \"./components/input.js\";\nexport { Input } from \"./components/input.js\";\nexport { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton } from \"./components/select.js\";\nexport { Tabs, TabsList, TabsTrigger, TabsContent } from \"./components/tabs.js\";\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from \"./components/tooltip.js\";\nexport { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField } from \"./components/form.js\";\nexport { Checkbox } from \"./components/checkbox.js\";\nexport { Switch } from \"./components/switch.js\";\nexport type { BadgeProps } from \"./components/badge.js\";\nexport { Badge, badgeVariants } from \"./components/badge.js\";\nexport { Avatar, AvatarImage, AvatarFallback } from \"./components/avatar.js\";\nexport { Separator } from \"./components/separator.js\";\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from \"./components/accordion.js\";\nexport { RadioGroup, RadioGroupItem } from \"./components/radio-group.js\";\nexport { Progress } from \"./components/progress.js\";\nexport { Slider } from \"./components/slider.js\";\nexport type { DatePickerProps } from \"./components/date-picker.js\";\nexport { DatePicker } from \"./components/date-picker.js\";\nexport type { DateTimePickerProps, DateTimePickerTime } from \"./components/date-time-picker.js\";\nexport { DateTimePicker } from \"./components/date-time-picker.js\";\nexport { ScrollArea, ScrollBar } from \"./components/scroll-area.js\";\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption } from \"./components/table.js\";\nexport { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription } from \"./components/sheet.js\";\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent } from \"./components/collapsible.js\";\nexport type { SearchDropdownProps } from \"./components/search-dropdown.js\";\nexport { SearchDropdown } from \"./components/search-dropdown.js\";\n\nexport type { ConsequenceDialogProps, ConsequenceDialogReason, ConsequenceDialogTypedConfirmation } from \"./components/consequence-dialog.js\";\nexport { ConsequenceDialog } from \"./components/consequence-dialog.js\";\n\nexport type { MaxWidthOption, ScreenAdaptiveViewProps } from \"./components/layout/screen-adaptive-view.js\";\nexport { ScreenAdaptiveView } from \"./components/layout/screen-adaptive-view.js\";\n\nexport { useMediaQuery } from \"./hooks/use-media-query.js\";\n\nexport { useAsyncAction } from \"./hooks/use-async-action.js\";\nexport type { AsyncAction, UseAsyncActionOptions } from \"./hooks/use-async-action.js\";\n\n// Shared building blocks\nexport { RelativeTime } from \"./components/relative-time.js\";\nexport type { RelativeTimeProps } from \"./components/relative-time.js\";\n\nexport { EndOfListIndicator } from \"./components/end-of-list-indicator.js\";\nexport type { EndOfListIndicatorProps } from \"./components/end-of-list-indicator.js\";\n\nexport { ScrollToTopButton } from \"./components/scroll-to-top-button.js\";\nexport type { ScrollToTopButtonProps } from \"./components/scroll-to-top-button.js\";\n\nexport { ChunkErrorRecovery } from \"./components/chunk-error-recovery.js\";\nexport type { ChunkErrorRecoveryProps } from \"./components/chunk-error-recovery.js\";\n\n// List pagination — the one Previous / counter / Next control plus the page-state\n// hooks for its two flavors (known total, and cursor/`hasMore`).\nexport { ListPagination } from \"./components/list-pagination.js\";\nexport type { ListPaginationProps } from \"./components/list-pagination.js\";\n\nexport { usePagedList } from \"./hooks/use-paged-list.js\";\nexport type { ListPaginationState, PagedList, UsePagedListOptions } from \"./hooks/use-paged-list.js\";\n\nexport { useCursorPage } from \"./hooks/use-cursor-page.js\";\nexport type { CursorPage, CursorPager, UseCursorPageOptions } from \"./hooks/use-cursor-page.js\";\n\n// Return-scroll: restore a list's window offset when the user comes back to it after a\n// route change (save on leave, restore once on an eligible + ready return, keyed by the\n// caller's canonical list URL).\nexport { useReturnScroll, saveReturnScroll, clearReturnScroll, readReturnScroll } from \"./hooks/use-return-scroll.js\";\nexport type { ReturnScrollEntry, UseReturnScrollOptions, UseReturnScrollResult } from \"./hooks/use-return-scroll.js\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/ui-core",
3
- "version": "0.11.20",
3
+ "version": "0.11.22",
4
4
  "description": "Shared UI components for TTT Productions projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -89,6 +89,7 @@
89
89
  "@radix-ui/react-tabs": "^1.1.13",
90
90
  "@radix-ui/react-toast": "^1.2.15",
91
91
  "@radix-ui/react-tooltip": "^1.2.8",
92
+ "@ttt-productions/input-format-core": "^0.1.1",
92
93
  "class-variance-authority": "^0.7.0",
93
94
  "clsx": "^2.1.1",
94
95
  "tailwind-merge": "^3.6.0"