@flytedan/flytebot-design-system 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -692,10 +692,11 @@ interface BannerProps {
692
692
  title: React.ReactNode;
693
693
  detail?: React.ReactNode;
694
694
  actions?: React.ReactNode;
695
- /** MP-243 Q3: a Banner reporting a failure (danger) or something invalid the user must
696
- * fix (warn) is a live-region announcement, not decoration — `role="alert"` for those
697
- * interrupts and announces immediately, `role="status"` for info/ok announces politely
698
- * once idle. Defaults by tone; a caller can still override either way. */
695
+ /** A Banner reporting a failure (danger) is a live-region announcement that interrupts
696
+ * and announces immediately (`role="alert"`); every other tone — warn, info, ok —
697
+ * announces politely once idle (`role="status"`), since a static warning or advisory
698
+ * banner isn't urgent enough to talk over whatever the user is doing. Defaults by
699
+ * tone; a caller can still override either way. */
699
700
  role?: "alert" | "status";
700
701
  }
701
702
  declare function Banner({ tone, title, detail, actions, role }: BannerProps): React.JSX.Element;
@@ -1080,7 +1081,7 @@ interface FilterFieldSelect extends FilterFieldBase {
1080
1081
  type: "select";
1081
1082
  options: FilterFieldOption[];
1082
1083
  }
1083
- /** MP-243: include/exclude, shared by every field whose value is a set of options. */
1084
+ /** Include/exclude, shared by every field whose value is a set of options. */
1084
1085
  type FilterOperator = "in" | "notIn";
1085
1086
  interface FilterMultiSelectValue {
1086
1087
  operator: FilterOperator;
@@ -1102,27 +1103,27 @@ interface FilterFieldMultiSelect extends FilterFieldBase {
1102
1103
  options?: FilterFieldOption[];
1103
1104
  /** Async option source for long lists (schools, imports, …), called ~300ms after the
1104
1105
  * last keystroke with the current query (possibly "") and an AbortSignal that fires
1105
- * once a newer query has superseded this call (MP-243 A6) — pass it straight to
1106
- * `fetch`/whatever transport does the lookup so a superseded request is actually
1107
- * cancelled, not just ignored on arrival. A rejection (that isn't the abort itself)
1108
- * renders as a visible error with a Retry action; it is never folded into "no
1109
- * matches", since a search that FAILED is a different thing to tell the user than a
1110
- * search that found nothing. */
1106
+ * once a newer query has superseded this call — pass it straight to `fetch`/whatever
1107
+ * transport does the lookup so a superseded request is actually cancelled, not just
1108
+ * ignored on arrival. A rejection (that isn't the abort itself) renders as a visible
1109
+ * error with a Retry action; it is never folded into "no matches", since a search
1110
+ * that FAILED is a different thing to tell the user than a search that found
1111
+ * nothing. */
1111
1112
  loadOptions?: (query: string, signal: AbortSignal) => Promise<FilterFieldOption[]>;
1112
- /** MP-243 A1: paste-a-list as a second input mode for THIS SAME field — search and
1113
- * paste both build one FilterMultiSelectValue for one dimension, so a school found by
1114
- * typing and a school found by pasting a list land as two entries in the same set
1115
- * instead of two separate filters that would AND together. Presence of `resolve` is
1116
- * what turns on the Search/Paste mode switch; omit it for a search-only field. Never
1117
- * called with an empty token list, and tokens are de-duplicated before the call. */
1113
+ /** Paste-a-list as a second input mode for THIS SAME field — search and paste both
1114
+ * build one FilterMultiSelectValue for one dimension, so a school found by typing and
1115
+ * a school found by pasting a list land as two entries in the same set instead of two
1116
+ * separate filters that would AND together. Presence of `resolve` is what turns on
1117
+ * the Search/Paste mode switch; omit it for a search-only field. Never called with an
1118
+ * empty token list, and tokens are de-duplicated before the call. */
1118
1119
  resolve?: (tokens: string[]) => Promise<FilterResolveResult>;
1119
1120
  placeholder?: string;
1120
1121
  /** Placeholder for the paste textarea, when `resolve` is given. Falls back to `placeholder`. */
1121
1122
  pastePlaceholder?: string;
1122
- /** MP-243 A2: restrict which operators this field's toggle offers, e.g. `["in"]` for a
1123
- * dimension that only ever means "is any of" and never "is none of" — a single-operator
1124
- * list hides the toggle entirely and that operator applies unconditionally, so an app
1125
- * can express an "any"/"is"-only dimension exactly rather than showing a switch with
1123
+ /** Restrict which operators this field's toggle offers, e.g. `["in"]` for a dimension
1124
+ * that only ever means "is any of" and never "is none of" — a single-operator list
1125
+ * hides the toggle entirely and that operator applies unconditionally, so an app can
1126
+ * express an "any"/"is"-only dimension exactly rather than showing a switch with
1126
1127
  * nothing to switch to. Defaults to both `["in", "notIn"]`. */
1127
1128
  operators?: FilterOperator[];
1128
1129
  }
@@ -1137,6 +1138,10 @@ interface FilterFieldRange extends FilterFieldBase {
1137
1138
  min?: number;
1138
1139
  max?: number;
1139
1140
  step?: number;
1141
+ /** Requires whole numbers for both bounds. Off by default, the same opt-in NumberInput
1142
+ * itself takes — a range field with a whole-number step (e.g. 10) may still want
1143
+ * decimal bounds, so this is a deliberate choice, not inferred from `step`. */
1144
+ integer?: boolean;
1140
1145
  minLabel?: string;
1141
1146
  maxLabel?: string;
1142
1147
  minPlaceholder?: string;
@@ -1153,16 +1158,24 @@ type FilterDateValue = {
1153
1158
  interface FilterFieldDate extends FilterFieldBase {
1154
1159
  type: "date";
1155
1160
  /** Default days shown the first time "within the last N days" is picked — shown, not
1156
- * written: nothing is set until the user changes it or confirms it (MP-243 A4). */
1161
+ * written: nothing is set until the user changes it or confirms it. Must be a whole
1162
+ * number from 1 to `maxDays`; FilterBar throws a clear, field-naming error in
1163
+ * development if it isn't. */
1157
1164
  defaultDays?: number;
1165
+ /** Upper bound enforced on the "within the last N days" input. Default 3650 (ten
1166
+ * years) — generous enough for any real "recent activity" window while still
1167
+ * guaranteeing a plausible, in-range value ever reaches `onFilterChange`. Must be a
1168
+ * whole number from 1 to 3650; FilterBar throws a clear, field-naming error in
1169
+ * development if it isn't. */
1170
+ maxDays?: number;
1158
1171
  }
1159
1172
  /** A declared filter field. Add a variant here (and a branch below) for a new value
1160
1173
  * picker. text/select keep their original bare-string value shape unchanged, so an
1161
1174
  * existing consumer needs no migration; every other variant's value is a small typed
1162
1175
  * object of its own (see FilterValue). */
1163
1176
  type FilterField = FilterFieldText | FilterFieldSelect | FilterFieldMultiSelect | FilterFieldRange | FilterFieldDate;
1164
- /** MP-243 A7: the value shape a given declared field reads/writes, so a consumer can type
1165
- * its own `filters` record precisely instead of `Record<string, unknown>` — e.g.
1177
+ /** The value shape a given declared field reads/writes, so a consumer can type its own
1178
+ * `filters` record precisely instead of `Record<string, unknown>` — e.g.
1166
1179
  * `Record<"schools", FilterValue<typeof schoolsField>>`. */
1167
1180
  type FilterValue<F extends FilterField> = F extends FilterFieldText ? string : F extends FilterFieldSelect ? string : F extends FilterFieldMultiSelect ? FilterMultiSelectValue : F extends FilterFieldRange ? FilterRangeValue : F extends FilterFieldDate ? FilterDateValue : never;
1168
1181
  interface FilterBarProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "onChange"> {
@@ -1175,7 +1188,7 @@ interface FilterBarProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "on
1175
1188
  /** Fires on every value edit. Maps 1:1 onto useServerTable's setFilter(id, value).
1176
1189
  * FilterBar only ever calls this with a COMPLETE, valid value for the field — an
1177
1190
  * emptied selection, a one-sided date range, an invalid numeric range or a cleared
1178
- * bound go through onFilterRemove (or nothing) instead (MP-243 A3). */
1191
+ * bound go through onFilterRemove (or nothing) instead. */
1179
1192
  onFilterChange: (key: string, value: unknown) => void;
1180
1193
  /** Fires when a chip's × is clicked, or when an edit leaves a field with no valid
1181
1194
  * value. Maps 1:1 onto useServerTable's clearFilter(id). */
@@ -1196,18 +1209,17 @@ interface FilterBarProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "on
1196
1209
  * (`panel`), the same way SortMenu holds `open` but never sort state itself.
1197
1210
  *
1198
1211
  * Every floating surface — a field's edit panel and the "add filter" menu — is a real
1199
- * Popover (or Menu-in-a-Popover) from this kit's own actions module (MP-243 A5): viewport
1200
- * clamping, nested-layer handling, Escape, and focus-return-to-the-chip all come from
1201
- * there, so FilterBar itself runs no document-level dismissal listener of its own. Only
1202
- * the text field's inline input manages its own dismissal (blur/Enter/Escape), since it
1203
- * isn't a floating panel at all.
1212
+ * Popover (or Menu-in-a-Popover) from this kit's own actions module: viewport clamping,
1213
+ * nested-layer handling, Escape, and focus-return-to-the-chip all come from there, so
1214
+ * FilterBar itself runs no document-level dismissal listener of its own. Only the text
1215
+ * field's inline input manages its own dismissal (blur/Enter/Escape), since it isn't a
1216
+ * floating panel at all.
1204
1217
  *
1205
- * MP-243 adds three variants on top of the original text/select, for audiences too big or
1206
- * too shaped for a single free-text box or a short dropdown: multiSelect (searchable, sync
1207
- * or async, with an optional paste-a-list mode for the same value), range (numeric
1208
- * min/max), and date ("within the last N days" or "between two dates"). text/select are
1209
- * unchanged and keep their bare-string value shape, so an existing consumer needs no
1210
- * migration.
1218
+ * FilterBar supports five field kinds: text, select, multiSelect (searchable, sync or
1219
+ * async, with an optional paste-a-list mode for the same value), range (numeric min/max),
1220
+ * and date ("within the last N days" or "between two dates"). text and select keep a bare
1221
+ * string value; multiSelect/range/date each read/write a small typed object of their own
1222
+ * (see FilterValue).
1211
1223
  */
1212
1224
  declare function FilterBar({ fields, filters, onFilterChange, onFilterRemove, align, className, ...rest }: FilterBarProps): React.JSX.Element;
1213
1225
 
@@ -2188,7 +2200,10 @@ interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement
2188
2200
  }
2189
2201
  declare function Textarea({ label, help, error, required, rows, disabled, id, className, style, ...rest }: TextareaProps): React.JSX.Element;
2190
2202
 
2191
- /** Numeric field with thousands formatting on blur, +/- steppers with hold-to-repeat acceleration, arrow keys (shift = bigStep), and min/max clamping. */
2203
+ /** Numeric field with thousands formatting on blur, +/- steppers with hold-to-repeat
2204
+ * acceleration and arrow keys (shift = bigStep) that snap to the step grid within
2205
+ * [min, max], and inline min/max/integer validation that keeps an invalid typed value
2206
+ * from ever reaching `onChange`. */
2192
2207
  interface NumberInputProps {
2193
2208
  label?: string;
2194
2209
  help?: string;
@@ -2210,6 +2225,12 @@ interface NumberInputProps {
2210
2225
  step?: number;
2211
2226
  /** Larger step used while holding the steppers or shift+arrows. */
2212
2227
  bigStep?: number;
2228
+ /** Requires a whole number. Off by default — a field with a whole-number `step` (e.g.
2229
+ * cents in steps of 10) may still want decimal input, so this is a deliberate opt-in
2230
+ * rather than inferred from `step`. When set, typed text, blur, the steppers and arrow
2231
+ * keys all reject or snap away from a fractional value; a fractional value can never
2232
+ * reach `onChange`. */
2233
+ integer?: boolean;
2213
2234
  /** Thousands-format the display when not editing. Default true. */
2214
2235
  format?: boolean;
2215
2236
  value?: number | string;
@@ -2218,11 +2239,17 @@ interface NumberInputProps {
2218
2239
  * (e.g. FilterBar's range/date panels) tells an unset bound apart from a typed
2219
2240
  * zero by this, not by testing for `0` itself. */
2220
2241
  onChange?: (e: any) => void;
2242
+ /** Called with the field's current validity every time it changes — true once the
2243
+ * typed text is empty or a legal number, false while it is out of bounds, non-integer
2244
+ * (when `integer` is set), or otherwise unparseable. Lets a parent (e.g. a panel with
2245
+ * its own Done button) know a value is mid-edit-and-invalid even though nothing has
2246
+ * been (or ever will be) passed to `onChange` for it. */
2247
+ onValidityChange?: (valid: boolean) => void;
2221
2248
  placeholder?: string;
2222
2249
  style?: any;
2223
2250
  className?: string;
2224
2251
  }
2225
- declare function NumberInput({ label, help, error, prefix, suffix, required, disabled, min, max, step, bigStep, value, onChange, format, className, style, placeholder, id, "aria-label": ariaLabel }: NumberInputProps): React.JSX.Element;
2252
+ declare function NumberInput({ label, help, error, prefix, suffix, required, disabled, min, max, step, bigStep, integer, value, onChange, onValidityChange, format, className, style, placeholder, id, "aria-label": ariaLabel }: NumberInputProps): React.JSX.Element;
2226
2253
 
2227
2254
  /** Rich popover select: animated listbox, keyboard nav + type-ahead, search over 8 options, option icons, descriptions, meta and groups. API-compatible with the old native select. */
2228
2255
  interface SelectOption {
package/dist/index.d.ts CHANGED
@@ -692,10 +692,11 @@ interface BannerProps {
692
692
  title: React.ReactNode;
693
693
  detail?: React.ReactNode;
694
694
  actions?: React.ReactNode;
695
- /** MP-243 Q3: a Banner reporting a failure (danger) or something invalid the user must
696
- * fix (warn) is a live-region announcement, not decoration — `role="alert"` for those
697
- * interrupts and announces immediately, `role="status"` for info/ok announces politely
698
- * once idle. Defaults by tone; a caller can still override either way. */
695
+ /** A Banner reporting a failure (danger) is a live-region announcement that interrupts
696
+ * and announces immediately (`role="alert"`); every other tone — warn, info, ok —
697
+ * announces politely once idle (`role="status"`), since a static warning or advisory
698
+ * banner isn't urgent enough to talk over whatever the user is doing. Defaults by
699
+ * tone; a caller can still override either way. */
699
700
  role?: "alert" | "status";
700
701
  }
701
702
  declare function Banner({ tone, title, detail, actions, role }: BannerProps): React.JSX.Element;
@@ -1080,7 +1081,7 @@ interface FilterFieldSelect extends FilterFieldBase {
1080
1081
  type: "select";
1081
1082
  options: FilterFieldOption[];
1082
1083
  }
1083
- /** MP-243: include/exclude, shared by every field whose value is a set of options. */
1084
+ /** Include/exclude, shared by every field whose value is a set of options. */
1084
1085
  type FilterOperator = "in" | "notIn";
1085
1086
  interface FilterMultiSelectValue {
1086
1087
  operator: FilterOperator;
@@ -1102,27 +1103,27 @@ interface FilterFieldMultiSelect extends FilterFieldBase {
1102
1103
  options?: FilterFieldOption[];
1103
1104
  /** Async option source for long lists (schools, imports, …), called ~300ms after the
1104
1105
  * last keystroke with the current query (possibly "") and an AbortSignal that fires
1105
- * once a newer query has superseded this call (MP-243 A6) — pass it straight to
1106
- * `fetch`/whatever transport does the lookup so a superseded request is actually
1107
- * cancelled, not just ignored on arrival. A rejection (that isn't the abort itself)
1108
- * renders as a visible error with a Retry action; it is never folded into "no
1109
- * matches", since a search that FAILED is a different thing to tell the user than a
1110
- * search that found nothing. */
1106
+ * once a newer query has superseded this call — pass it straight to `fetch`/whatever
1107
+ * transport does the lookup so a superseded request is actually cancelled, not just
1108
+ * ignored on arrival. A rejection (that isn't the abort itself) renders as a visible
1109
+ * error with a Retry action; it is never folded into "no matches", since a search
1110
+ * that FAILED is a different thing to tell the user than a search that found
1111
+ * nothing. */
1111
1112
  loadOptions?: (query: string, signal: AbortSignal) => Promise<FilterFieldOption[]>;
1112
- /** MP-243 A1: paste-a-list as a second input mode for THIS SAME field — search and
1113
- * paste both build one FilterMultiSelectValue for one dimension, so a school found by
1114
- * typing and a school found by pasting a list land as two entries in the same set
1115
- * instead of two separate filters that would AND together. Presence of `resolve` is
1116
- * what turns on the Search/Paste mode switch; omit it for a search-only field. Never
1117
- * called with an empty token list, and tokens are de-duplicated before the call. */
1113
+ /** Paste-a-list as a second input mode for THIS SAME field — search and paste both
1114
+ * build one FilterMultiSelectValue for one dimension, so a school found by typing and
1115
+ * a school found by pasting a list land as two entries in the same set instead of two
1116
+ * separate filters that would AND together. Presence of `resolve` is what turns on
1117
+ * the Search/Paste mode switch; omit it for a search-only field. Never called with an
1118
+ * empty token list, and tokens are de-duplicated before the call. */
1118
1119
  resolve?: (tokens: string[]) => Promise<FilterResolveResult>;
1119
1120
  placeholder?: string;
1120
1121
  /** Placeholder for the paste textarea, when `resolve` is given. Falls back to `placeholder`. */
1121
1122
  pastePlaceholder?: string;
1122
- /** MP-243 A2: restrict which operators this field's toggle offers, e.g. `["in"]` for a
1123
- * dimension that only ever means "is any of" and never "is none of" — a single-operator
1124
- * list hides the toggle entirely and that operator applies unconditionally, so an app
1125
- * can express an "any"/"is"-only dimension exactly rather than showing a switch with
1123
+ /** Restrict which operators this field's toggle offers, e.g. `["in"]` for a dimension
1124
+ * that only ever means "is any of" and never "is none of" — a single-operator list
1125
+ * hides the toggle entirely and that operator applies unconditionally, so an app can
1126
+ * express an "any"/"is"-only dimension exactly rather than showing a switch with
1126
1127
  * nothing to switch to. Defaults to both `["in", "notIn"]`. */
1127
1128
  operators?: FilterOperator[];
1128
1129
  }
@@ -1137,6 +1138,10 @@ interface FilterFieldRange extends FilterFieldBase {
1137
1138
  min?: number;
1138
1139
  max?: number;
1139
1140
  step?: number;
1141
+ /** Requires whole numbers for both bounds. Off by default, the same opt-in NumberInput
1142
+ * itself takes — a range field with a whole-number step (e.g. 10) may still want
1143
+ * decimal bounds, so this is a deliberate choice, not inferred from `step`. */
1144
+ integer?: boolean;
1140
1145
  minLabel?: string;
1141
1146
  maxLabel?: string;
1142
1147
  minPlaceholder?: string;
@@ -1153,16 +1158,24 @@ type FilterDateValue = {
1153
1158
  interface FilterFieldDate extends FilterFieldBase {
1154
1159
  type: "date";
1155
1160
  /** Default days shown the first time "within the last N days" is picked — shown, not
1156
- * written: nothing is set until the user changes it or confirms it (MP-243 A4). */
1161
+ * written: nothing is set until the user changes it or confirms it. Must be a whole
1162
+ * number from 1 to `maxDays`; FilterBar throws a clear, field-naming error in
1163
+ * development if it isn't. */
1157
1164
  defaultDays?: number;
1165
+ /** Upper bound enforced on the "within the last N days" input. Default 3650 (ten
1166
+ * years) — generous enough for any real "recent activity" window while still
1167
+ * guaranteeing a plausible, in-range value ever reaches `onFilterChange`. Must be a
1168
+ * whole number from 1 to 3650; FilterBar throws a clear, field-naming error in
1169
+ * development if it isn't. */
1170
+ maxDays?: number;
1158
1171
  }
1159
1172
  /** A declared filter field. Add a variant here (and a branch below) for a new value
1160
1173
  * picker. text/select keep their original bare-string value shape unchanged, so an
1161
1174
  * existing consumer needs no migration; every other variant's value is a small typed
1162
1175
  * object of its own (see FilterValue). */
1163
1176
  type FilterField = FilterFieldText | FilterFieldSelect | FilterFieldMultiSelect | FilterFieldRange | FilterFieldDate;
1164
- /** MP-243 A7: the value shape a given declared field reads/writes, so a consumer can type
1165
- * its own `filters` record precisely instead of `Record<string, unknown>` — e.g.
1177
+ /** The value shape a given declared field reads/writes, so a consumer can type its own
1178
+ * `filters` record precisely instead of `Record<string, unknown>` — e.g.
1166
1179
  * `Record<"schools", FilterValue<typeof schoolsField>>`. */
1167
1180
  type FilterValue<F extends FilterField> = F extends FilterFieldText ? string : F extends FilterFieldSelect ? string : F extends FilterFieldMultiSelect ? FilterMultiSelectValue : F extends FilterFieldRange ? FilterRangeValue : F extends FilterFieldDate ? FilterDateValue : never;
1168
1181
  interface FilterBarProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "onChange"> {
@@ -1175,7 +1188,7 @@ interface FilterBarProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "on
1175
1188
  /** Fires on every value edit. Maps 1:1 onto useServerTable's setFilter(id, value).
1176
1189
  * FilterBar only ever calls this with a COMPLETE, valid value for the field — an
1177
1190
  * emptied selection, a one-sided date range, an invalid numeric range or a cleared
1178
- * bound go through onFilterRemove (or nothing) instead (MP-243 A3). */
1191
+ * bound go through onFilterRemove (or nothing) instead. */
1179
1192
  onFilterChange: (key: string, value: unknown) => void;
1180
1193
  /** Fires when a chip's × is clicked, or when an edit leaves a field with no valid
1181
1194
  * value. Maps 1:1 onto useServerTable's clearFilter(id). */
@@ -1196,18 +1209,17 @@ interface FilterBarProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "on
1196
1209
  * (`panel`), the same way SortMenu holds `open` but never sort state itself.
1197
1210
  *
1198
1211
  * Every floating surface — a field's edit panel and the "add filter" menu — is a real
1199
- * Popover (or Menu-in-a-Popover) from this kit's own actions module (MP-243 A5): viewport
1200
- * clamping, nested-layer handling, Escape, and focus-return-to-the-chip all come from
1201
- * there, so FilterBar itself runs no document-level dismissal listener of its own. Only
1202
- * the text field's inline input manages its own dismissal (blur/Enter/Escape), since it
1203
- * isn't a floating panel at all.
1212
+ * Popover (or Menu-in-a-Popover) from this kit's own actions module: viewport clamping,
1213
+ * nested-layer handling, Escape, and focus-return-to-the-chip all come from there, so
1214
+ * FilterBar itself runs no document-level dismissal listener of its own. Only the text
1215
+ * field's inline input manages its own dismissal (blur/Enter/Escape), since it isn't a
1216
+ * floating panel at all.
1204
1217
  *
1205
- * MP-243 adds three variants on top of the original text/select, for audiences too big or
1206
- * too shaped for a single free-text box or a short dropdown: multiSelect (searchable, sync
1207
- * or async, with an optional paste-a-list mode for the same value), range (numeric
1208
- * min/max), and date ("within the last N days" or "between two dates"). text/select are
1209
- * unchanged and keep their bare-string value shape, so an existing consumer needs no
1210
- * migration.
1218
+ * FilterBar supports five field kinds: text, select, multiSelect (searchable, sync or
1219
+ * async, with an optional paste-a-list mode for the same value), range (numeric min/max),
1220
+ * and date ("within the last N days" or "between two dates"). text and select keep a bare
1221
+ * string value; multiSelect/range/date each read/write a small typed object of their own
1222
+ * (see FilterValue).
1211
1223
  */
1212
1224
  declare function FilterBar({ fields, filters, onFilterChange, onFilterRemove, align, className, ...rest }: FilterBarProps): React.JSX.Element;
1213
1225
 
@@ -2188,7 +2200,10 @@ interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement
2188
2200
  }
2189
2201
  declare function Textarea({ label, help, error, required, rows, disabled, id, className, style, ...rest }: TextareaProps): React.JSX.Element;
2190
2202
 
2191
- /** Numeric field with thousands formatting on blur, +/- steppers with hold-to-repeat acceleration, arrow keys (shift = bigStep), and min/max clamping. */
2203
+ /** Numeric field with thousands formatting on blur, +/- steppers with hold-to-repeat
2204
+ * acceleration and arrow keys (shift = bigStep) that snap to the step grid within
2205
+ * [min, max], and inline min/max/integer validation that keeps an invalid typed value
2206
+ * from ever reaching `onChange`. */
2192
2207
  interface NumberInputProps {
2193
2208
  label?: string;
2194
2209
  help?: string;
@@ -2210,6 +2225,12 @@ interface NumberInputProps {
2210
2225
  step?: number;
2211
2226
  /** Larger step used while holding the steppers or shift+arrows. */
2212
2227
  bigStep?: number;
2228
+ /** Requires a whole number. Off by default — a field with a whole-number `step` (e.g.
2229
+ * cents in steps of 10) may still want decimal input, so this is a deliberate opt-in
2230
+ * rather than inferred from `step`. When set, typed text, blur, the steppers and arrow
2231
+ * keys all reject or snap away from a fractional value; a fractional value can never
2232
+ * reach `onChange`. */
2233
+ integer?: boolean;
2213
2234
  /** Thousands-format the display when not editing. Default true. */
2214
2235
  format?: boolean;
2215
2236
  value?: number | string;
@@ -2218,11 +2239,17 @@ interface NumberInputProps {
2218
2239
  * (e.g. FilterBar's range/date panels) tells an unset bound apart from a typed
2219
2240
  * zero by this, not by testing for `0` itself. */
2220
2241
  onChange?: (e: any) => void;
2242
+ /** Called with the field's current validity every time it changes — true once the
2243
+ * typed text is empty or a legal number, false while it is out of bounds, non-integer
2244
+ * (when `integer` is set), or otherwise unparseable. Lets a parent (e.g. a panel with
2245
+ * its own Done button) know a value is mid-edit-and-invalid even though nothing has
2246
+ * been (or ever will be) passed to `onChange` for it. */
2247
+ onValidityChange?: (valid: boolean) => void;
2221
2248
  placeholder?: string;
2222
2249
  style?: any;
2223
2250
  className?: string;
2224
2251
  }
2225
- declare function NumberInput({ label, help, error, prefix, suffix, required, disabled, min, max, step, bigStep, value, onChange, format, className, style, placeholder, id, "aria-label": ariaLabel }: NumberInputProps): React.JSX.Element;
2252
+ declare function NumberInput({ label, help, error, prefix, suffix, required, disabled, min, max, step, bigStep, integer, value, onChange, onValidityChange, format, className, style, placeholder, id, "aria-label": ariaLabel }: NumberInputProps): React.JSX.Element;
2226
2253
 
2227
2254
  /** Rich popover select: animated listbox, keyboard nav + type-ahead, search over 8 options, option icons, descriptions, meta and groups. API-compatible with the old native select. */
2228
2255
  interface SelectOption {