@gooddata/sdk-ui-kit 11.52.0-alpha.4 → 11.52.0-alpha.6

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.
Files changed (51) hide show
  1. package/esm/@ui/UiAsyncTable/UiAsyncTable/UiAsyncTableBody.d.ts.map +1 -1
  2. package/esm/@ui/UiAsyncTable/UiAsyncTable/UiAsyncTableBody.js +9 -6
  3. package/esm/@ui/UiAsyncTable/UiAsyncTable/UiAsyncTableRow.d.ts.map +1 -1
  4. package/esm/@ui/UiAsyncTable/UiAsyncTable/UiAsyncTableRow.js +13 -2
  5. package/esm/@ui/UiAsyncTable/UiAsyncTable/asyncTableGridContext.d.ts +4 -0
  6. package/esm/@ui/UiAsyncTable/UiAsyncTable/asyncTableGridContext.d.ts.map +1 -0
  7. package/esm/@ui/UiAsyncTable/UiAsyncTable/asyncTableGridContext.js +12 -0
  8. package/esm/@ui/UiCombobox/UiComboboxInput.d.ts +2 -0
  9. package/esm/@ui/UiCombobox/UiComboboxInput.d.ts.map +1 -1
  10. package/esm/@ui/UiCombobox/UiComboboxInput.js +2 -2
  11. package/esm/@ui/UiMenu/components/defaults/DefaultUiMenuInteractiveItem.d.ts.map +1 -1
  12. package/esm/@ui/UiMenu/components/defaults/DefaultUiMenuInteractiveItem.js +25 -22
  13. package/esm/@ui/UiMenu/types.d.ts +1 -0
  14. package/esm/@ui/UiMenu/types.d.ts.map +1 -1
  15. package/esm/@ui/UiTextInput/UiTextInput.d.ts +3 -1
  16. package/esm/@ui/UiTextInput/UiTextInput.d.ts.map +1 -1
  17. package/esm/@ui/UiTextInput/UiTextInput.js +2 -2
  18. package/esm/Datepicker/Datepicker.d.ts +3 -52
  19. package/esm/Datepicker/Datepicker.d.ts.map +1 -1
  20. package/esm/Datepicker/Datepicker.js +142 -180
  21. package/esm/Dialog/StylingEditorDialog/StylingEditorDialog.d.ts +9 -0
  22. package/esm/Dialog/StylingEditorDialog/StylingEditorDialog.d.ts.map +1 -1
  23. package/esm/Dialog/StylingEditorDialog/StylingEditorDialog.js +33 -28
  24. package/esm/Dropdown/DropdownButton.d.ts.map +1 -1
  25. package/esm/Dropdown/DropdownButton.js +5 -0
  26. package/esm/Form/InputWithNumberFormat.d.ts +0 -1
  27. package/esm/Form/InputWithNumberFormat.d.ts.map +1 -1
  28. package/esm/Form/InputWithNumberFormat.js +12 -18
  29. package/esm/Header/Header.d.ts.map +1 -1
  30. package/esm/Header/Header.js +2 -2
  31. package/esm/Header/HeaderChatButton.d.ts +9 -1
  32. package/esm/Header/HeaderChatButton.d.ts.map +1 -1
  33. package/esm/Header/HeaderChatButton.js +12 -1
  34. package/esm/Header/typings.d.ts +5 -0
  35. package/esm/Header/typings.d.ts.map +1 -1
  36. package/esm/Header/typings.js +1 -1
  37. package/esm/Timezone/TimezoneSelect.d.ts +10 -2
  38. package/esm/Timezone/TimezoneSelect.d.ts.map +1 -1
  39. package/esm/Timezone/TimezoneSelect.js +4 -3
  40. package/esm/index.d.ts +1 -1
  41. package/esm/index.d.ts.map +1 -1
  42. package/esm/index.js +1 -1
  43. package/esm/sdk-ui-kit.d.ts +37 -2
  44. package/package.json +11 -11
  45. package/src/@ui/UiMenu/UiMenu.scss +1 -1
  46. package/src/@ui/UiTextInput/UiTextInput.scss +7 -0
  47. package/styles/css/main.css +7 -1
  48. package/styles/css/main.css.map +1 -1
  49. package/styles/css/stylingEditorDialog.css +3 -0
  50. package/styles/css/stylingEditorDialog.css.map +1 -1
  51. package/styles/scss/stylingEditorDialog.scss +4 -0
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // (C) 2020-2026 GoodData Corporation
3
- import { PureComponent, createRef, } from "react";
3
+ import { PureComponent, memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
4
4
  import classNames from "classnames";
5
5
  import { format, isSameDay, isValid, parse } from "date-fns";
6
6
  import { de, enAU, enGB, enUS, es, fi, fr, frCA, id, it, ja, ko, nl, pl, pt, ptBR, ru, sl, th, tr, uk, vi, zhCN, } from "date-fns/locale";
@@ -81,191 +81,147 @@ function convertWeekStart(weekStart) {
81
81
  throw new Error(`Unknown week start ${weekStart}`);
82
82
  }
83
83
  }
84
- export class WrappedDatePicker extends PureComponent {
85
- rootRef = null;
86
- datePickerContainerRef = createRef();
87
- inputRef = createRef();
88
- datePickerId = uuid();
89
- static defaultProps = {
90
- className: "",
91
- date: new Date(),
92
- placeholder: "",
93
- onChange: () => { },
94
- onBlur: () => { },
95
- resetOnInvalidValue: false,
96
- size: "",
97
- tabIndex: 0,
98
- alignPoints: [{ align: "bl tl" }, { align: "br tr" }, { align: "tl bl" }, { align: "tr br" }],
99
- onAlign: () => { },
100
- dateFormat: DEFAULT_DATE_FORMAT,
101
- weekStart: "Sunday",
102
- };
103
- constructor(props) {
104
- super(props);
105
- const { alignPoints, date, dateFormat } = props;
106
- this.state = {
107
- align: alignPoints?.[0]?.align ?? "bl tl",
108
- selectedDate: date,
109
- monthDate: date,
110
- inputValue: formatDate(date || new Date(), dateFormat ?? DEFAULT_DATE_FORMAT),
111
- isOpen: false,
112
- };
113
- this.handleDayChanged = this.handleDayChanged.bind(this);
114
- this.handleMonthChanged = this.handleMonthChanged.bind(this);
115
- this.handleInputChanged = this.handleInputChanged.bind(this);
116
- this.handleInputBlur = this.handleInputBlur.bind(this);
117
- this.alignDatePicker = this.alignDatePicker.bind(this);
118
- this.setComponentRef = this.setComponentRef.bind(this);
119
- this.handleWrapperClick = this.handleWrapperClick.bind(this);
120
- this.handleClickOutside = this.handleClickOutside.bind(this);
121
- this.onKeyDown = this.onKeyDown.bind(this);
122
- this.handleCustomDayClick = this.handleCustomDayClick.bind(this);
123
- }
124
- componentDidMount() {
125
- const { date, dateFormat } = this.props;
126
- this.setState({ selectedDate: this.updateDate(date || new Date()) });
127
- this.setState({ inputValue: formatDate(date || new Date(), dateFormat ?? DEFAULT_DATE_FORMAT) });
128
- window.addEventListener("resize", this.resizeHandler);
129
- document.addEventListener("mousedown", this.handleClickOutside);
130
- }
131
- UNSAFE_componentWillReceiveProps(nextProps) {
132
- const { props } = this;
133
- const propsDate = props.date?.getTime() ?? 0;
134
- const nextPropsDate = nextProps.date?.getTime() ?? 0;
135
- if (propsDate !== nextPropsDate) {
136
- const selectedDate = this.updateDate(nextProps.date || new Date());
137
- this.setState({ selectedDate });
138
- this.setState({ monthDate: selectedDate });
139
- this.setState({ inputValue: formatDate(selectedDate, props.dateFormat ?? DEFAULT_DATE_FORMAT) });
140
- }
141
- }
142
- componentWillUnmount() {
143
- window.removeEventListener("resize", this.resizeHandler);
144
- document.removeEventListener("mousedown", this.handleClickOutside);
84
+ function normalizeDate(date) {
85
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
86
+ }
87
+ const DEFAULT_ALIGN_POINTS = [
88
+ { align: "bl tl" },
89
+ { align: "br tr" },
90
+ { align: "tl bl" },
91
+ { align: "tr br" },
92
+ ];
93
+ // evaluated once per module load, mirroring the original static defaultProps
94
+ const DEFAULT_DATE = new Date();
95
+ function WrappedDatePickerCore({ accessibilityConfig, date: dateProp, className = "", placeholder = "", onChange, onBlur, onValidateInput, resetOnInvalidValue = false, size = "", tabIndex = 0, alignPoints: alignPointsProp, onAlign, dateFormat: dateFormatProp, weekStart: weekStartProp, onDateInputKeyDown, intl, }) {
96
+ // these props are nullable at runtime (untyped callers do pass null explicitly), so plain default
97
+ // values are not enough - the class component guarded every usage with `||`, `??` or optional calls
98
+ const date = dateProp || DEFAULT_DATE;
99
+ const alignPoints = alignPointsProp ?? DEFAULT_ALIGN_POINTS;
100
+ const dateFormat = dateFormatProp ?? DEFAULT_DATE_FORMAT;
101
+ const weekStart = weekStartProp ?? "Sunday";
102
+ const rootRef = useRef(null);
103
+ const datePickerContainerRef = useRef(null);
104
+ const inputRef = useRef(null);
105
+ const datePickerId = useMemo(() => uuid(), []);
106
+ const [align, setAlign] = useState(alignPoints[0]?.align ?? "bl tl");
107
+ const [selectedDate, setSelectedDate] = useState(() => normalizeDate(date));
108
+ const [monthDate, setMonthDate] = useState(date);
109
+ const [inputValue, setInputValue] = useState(() => formatDate(date, dateFormat));
110
+ const [isOpen, setIsOpen] = useState(false);
111
+ // resync the derived state whenever the date coming from the props changes
112
+ const [prevDateTime, setPrevDateTime] = useState(date.getTime());
113
+ if (date.getTime() !== prevDateTime) {
114
+ const newlySelectedDate = normalizeDate(date);
115
+ setPrevDateTime(date.getTime());
116
+ setSelectedDate(newlySelectedDate);
117
+ setMonthDate(newlySelectedDate);
118
+ setInputValue(formatDate(newlySelectedDate, dateFormat));
145
119
  }
146
- handleClickOutside(event) {
147
- if (this.datePickerContainerRef.current &&
148
- !this.datePickerContainerRef.current.contains(event.target) &&
149
- this.inputRef.current &&
150
- !this.inputRef.current.contains(event.target)) {
151
- this.setState({ isOpen: false });
120
+ const alignDatePicker = useCallback(() => {
121
+ const container = datePickerContainerRef.current?.parentElement;
122
+ if (!alignPoints || !container || !rootRef.current) {
123
+ return;
152
124
  }
153
- }
154
- componentDidUpdate(_prevProps, prevState) {
155
- if (this.state.isOpen && !prevState.isOpen) {
156
- this.alignDatePicker();
125
+ const optimalAlignment = getOptimalAlignment({
126
+ targetRegion: elementRegion(rootRef.current),
127
+ selfRegion: elementRegion(container),
128
+ alignPoints,
129
+ });
130
+ const { align: optimalAlign } = optimalAlignment.alignment;
131
+ setAlign(optimalAlign);
132
+ onAlign?.(optimalAlign);
133
+ }, [alignPoints, onAlign]);
134
+ // keeps the debounced resize handler and the "just opened" effect stable while still
135
+ // calling the up-to-date alignment logic, the same way the class instance method did
136
+ const alignDatePickerRef = useRef(alignDatePicker);
137
+ // must run before the "just opened" layout effect below, hence useLayoutEffect
138
+ useLayoutEffect(() => {
139
+ alignDatePickerRef.current = alignDatePicker;
140
+ }, [alignDatePicker]);
141
+ const resizeHandler = useMemo(() => debounce(() => alignDatePickerRef.current(), 100), []);
142
+ useEffect(() => {
143
+ window.addEventListener("resize", resizeHandler);
144
+ return () => {
145
+ resizeHandler.cancel();
146
+ window.removeEventListener("resize", resizeHandler);
147
+ };
148
+ }, [resizeHandler]);
149
+ useEffect(() => {
150
+ const handleClickOutside = (event) => {
151
+ if (datePickerContainerRef.current &&
152
+ !datePickerContainerRef.current.contains(event.target) &&
153
+ inputRef.current &&
154
+ !inputRef.current.contains(event.target)) {
155
+ setIsOpen(false);
156
+ }
157
+ };
158
+ document.addEventListener("mousedown", handleClickOutside);
159
+ return () => {
160
+ document.removeEventListener("mousedown", handleClickOutside);
161
+ };
162
+ }, []);
163
+ useLayoutEffect(() => {
164
+ if (isOpen) {
165
+ alignDatePickerRef.current();
157
166
  }
158
- }
159
- setComponentRef(ref) {
160
- this.rootRef = ref;
161
- }
162
- getInputClasses() {
163
- return classNames("input-text", "small-12", this.props.size, `gd-datepicker-input-${this.datePickerId}`);
164
- }
165
- getComponentClasses() {
166
- return classNames("gd-datepicker", this.props.className, this.props.size, "gd-datepicker-input", this.state.isOpen ? "gd-datepicker-focused" : "");
167
- }
168
- getOverlayWrapperClasses() {
169
- const [inputAnchorPoint, pickerAnchorPoint] = this.state.align.split(" ");
170
- return classNames("gd-datepicker-picker", "gd-datepicker-OverlayWrapper", `gd-datepicker-OverlayWrapper-${inputAnchorPoint}-xx`, `gd-datepicker-OverlayWrapper-xx-${pickerAnchorPoint}`);
171
- }
172
- resizeHandler = debounce(() => this.alignDatePicker(), 100);
173
- updateDate(date) {
174
- return this.normalizeDate(date);
175
- }
176
- handleInputBlur(e) {
177
- this.props.onBlur?.(e.target.value);
178
- }
179
- handleInputChanged(e) {
167
+ }, [isOpen]);
168
+ const handleInputBlur = useCallback((e) => {
169
+ onBlur?.(e.target.value);
170
+ }, [onBlur]);
171
+ const handleInputChanged = useCallback((e) => {
180
172
  const { value } = e.target;
181
- const parsedDate = parseDate(value, this.props.dateFormat ?? DEFAULT_DATE_FORMAT);
182
- this.props.onValidateInput?.(value);
183
- this.setState({ inputValue: value });
173
+ const parsedDate = parseDate(value, dateFormat);
174
+ onValidateInput?.(value);
175
+ setInputValue(value);
184
176
  if (parsedDate) {
185
- this.setState({
186
- selectedDate: parsedDate,
187
- monthDate: parsedDate,
188
- }, () => {
189
- if (this.state.selectedDate) {
190
- this.props.onChange?.(this.state.selectedDate);
191
- }
192
- });
177
+ setSelectedDate(parsedDate);
178
+ setMonthDate(parsedDate);
179
+ onChange?.(parsedDate);
180
+ return;
193
181
  }
194
- else {
195
- if (this.props.resetOnInvalidValue) {
196
- this.setState({
197
- selectedDate: this.state.selectedDate,
198
- monthDate: this.state.selectedDate,
199
- });
200
- return;
201
- }
202
- this.setState({
203
- selectedDate: undefined,
204
- monthDate: undefined,
205
- }, () => {
206
- // Signal invalid state by passing null
207
- this.props.onChange?.(null);
208
- });
182
+ if (resetOnInvalidValue) {
183
+ setMonthDate(selectedDate);
184
+ return;
209
185
  }
210
- }
211
- handleDayChanged(newlySelectedDate) {
186
+ setSelectedDate(undefined);
187
+ setMonthDate(undefined);
188
+ // Signal invalid state by passing null
189
+ onChange?.(null);
190
+ }, [dateFormat, onChange, onValidateInput, resetOnInvalidValue, selectedDate]);
191
+ const handleDayChanged = useCallback((newlySelectedDate) => {
212
192
  if (!newlySelectedDate) {
213
- this.setState({ isOpen: false });
193
+ setIsOpen(false);
214
194
  return;
215
195
  }
216
- if (this.state.selectedDate && isSameDay(this.state.selectedDate, newlySelectedDate)) {
217
- this.setState({ isOpen: false });
196
+ if (selectedDate && isSameDay(selectedDate, newlySelectedDate)) {
197
+ setIsOpen(false);
218
198
  return;
219
199
  }
220
- this.inputRef.current?.focus();
221
- this.props.onValidateInput?.(formatDate(newlySelectedDate, this.props.dateFormat ?? DEFAULT_DATE_FORMAT));
222
- this.setState({
223
- selectedDate: newlySelectedDate,
224
- monthDate: newlySelectedDate,
225
- inputValue: formatDate(newlySelectedDate, this.props.dateFormat ?? DEFAULT_DATE_FORMAT),
226
- isOpen: false,
227
- }, () => {
228
- this.props.onChange?.(newlySelectedDate);
229
- });
230
- }
231
- handleMonthChanged(month) {
232
- this.inputRef.current?.focus();
233
- this.setState({ monthDate: month });
234
- }
235
- handleCustomDayClick = (day, _modifiers) => {
200
+ inputRef.current?.focus();
201
+ onValidateInput?.(formatDate(newlySelectedDate, dateFormat));
202
+ setSelectedDate(newlySelectedDate);
203
+ setMonthDate(newlySelectedDate);
204
+ setInputValue(formatDate(newlySelectedDate, dateFormat));
205
+ setIsOpen(false);
206
+ onChange?.(newlySelectedDate);
207
+ }, [dateFormat, onChange, onValidateInput, selectedDate]);
208
+ const handleMonthChanged = useCallback((month) => {
209
+ inputRef.current?.focus();
210
+ setMonthDate(month);
211
+ }, []);
212
+ const handleCustomDayClick = useCallback((day, _modifiers) => {
236
213
  // Handle all day clicks, including outside days
237
- this.handleDayChanged(day);
238
- };
239
- normalizeDate(date) {
240
- return new Date(date.getFullYear(), date.getMonth(), date.getDate());
241
- }
242
- alignDatePicker() {
243
- const { alignPoints } = this.props;
244
- const container = this.datePickerContainerRef.current?.parentElement;
245
- if (!alignPoints || !container || !this.rootRef) {
246
- return;
247
- }
248
- const optimalAlignment = getOptimalAlignment({
249
- targetRegion: elementRegion(this.rootRef),
250
- selfRegion: elementRegion(container),
251
- alignPoints,
252
- });
253
- const { align } = optimalAlignment.alignment;
254
- this.setState({
255
- align,
256
- }, () => {
257
- this.props.onAlign?.(align);
258
- });
259
- }
260
- onKeyDown(e) {
214
+ handleDayChanged(day);
215
+ }, [handleDayChanged]);
216
+ const handleKeyDown = useCallback((e) => {
261
217
  if (e.key === "Escape" || e.key === "Tab") {
262
- this.setState({ isOpen: false });
218
+ setIsOpen(false);
263
219
  }
264
220
  if (isEnterKey(e)) {
265
- this.props.onDateInputKeyDown?.(e);
221
+ onDateInputKeyDown?.(e);
266
222
  }
267
- }
268
- handleWrapperClick(e) {
223
+ }, [onDateInputKeyDown]);
224
+ const handleWrapperClick = useCallback((e) => {
269
225
  const { classList } = e.target;
270
226
  /**
271
227
  * Prevent default fixes bug BB-332 but prevents in closing other dropdowns (Bug BB-1102)
@@ -274,19 +230,25 @@ export class WrappedDatePicker extends PureComponent {
274
230
  if (e.target && classList?.contains(DATEPICKER_OUTSIDE_DAY_SELECTOR)) {
275
231
  e.preventDefault();
276
232
  }
277
- }
278
- render() {
279
- const { inputValue, selectedDate, monthDate, isOpen } = this.state;
280
- const { accessibilityConfig, placeholder, intl, tabIndex } = this.props;
281
- const classNamesProps = {
282
- root: this.getOverlayWrapperClasses(),
233
+ }, []);
234
+ const handleInputClick = useCallback(() => {
235
+ setIsOpen(true);
236
+ }, []);
237
+ const dayPickerClassNames = useMemo(() => {
238
+ const [inputAnchorPoint, pickerAnchorPoint] = align.split(" ");
239
+ return {
240
+ root: classNames("gd-datepicker-picker", "gd-datepicker-OverlayWrapper", `gd-datepicker-OverlayWrapper-${inputAnchorPoint}-xx`, `gd-datepicker-OverlayWrapper-xx-${pickerAnchorPoint}`),
283
241
  };
284
- return (_jsxs("div", { "data-testid": "datepicker", className: this.getComponentClasses(), ref: this.setComponentRef, onClick: this.handleWrapperClick, children: [
285
- _jsx("input", { autoComplete: "off", "aria-labelledby": accessibilityConfig?.ariaLabelledBy, "aria-label": accessibilityConfig?.ariaLabel ||
286
- intl.formatMessage({ id: "datePicker.accessibility.label" }), "aria-describedby": accessibilityConfig?.ariaDescribedBy, onKeyDown: this.onKeyDown, tabIndex: tabIndex, onClick: () => this.setState({ isOpen: true }), ref: this.inputRef, value: inputValue, className: this.getInputClasses(), placeholder: placeholder, onChange: this.handleInputChanged, onBlur: this.handleInputBlur }), isOpen ? (_jsx("div", { id: `datepicker-popup-${this.datePickerId}`, role: "dialog", ref: this.datePickerContainerRef, children: _jsx(DayPicker, { classNames: classNamesProps, locale: convertLocale(intl.locale), showOutsideDays: true, mode: "single", selected: selectedDate, month: monthDate, onMonthChange: this.handleMonthChanged, weekStartsOn: convertWeekStart(this.props.weekStart ?? "Sunday"), onDayClick: this.handleCustomDayClick }) })) : null, _jsx("span", { className: "gd-datepicker-icon gd-icon-calendar" })
287
- ] }));
288
- }
242
+ }, [align]);
243
+ const componentClasses = classNames("gd-datepicker", className, size, "gd-datepicker-input", isOpen ? "gd-datepicker-focused" : "");
244
+ const inputClasses = classNames("input-text", "small-12", size, `gd-datepicker-input-${datePickerId}`);
245
+ return (_jsxs("div", { "data-testid": "datepicker", className: componentClasses, ref: rootRef, onClick: handleWrapperClick, children: [
246
+ _jsx("input", { autoComplete: "off", "aria-labelledby": accessibilityConfig?.ariaLabelledBy, "aria-label": accessibilityConfig?.ariaLabel ||
247
+ intl.formatMessage({ id: "datePicker.accessibility.label" }), "aria-describedby": accessibilityConfig?.ariaDescribedBy, onKeyDown: handleKeyDown, tabIndex: tabIndex, onClick: handleInputClick, ref: inputRef, value: inputValue, className: inputClasses, placeholder: placeholder, onChange: handleInputChanged, onBlur: handleInputBlur }), isOpen ? (_jsx("div", { id: `datepicker-popup-${datePickerId}`, role: "dialog", ref: datePickerContainerRef, children: _jsx(DayPicker, { classNames: dayPickerClassNames, locale: convertLocale(intl.locale), showOutsideDays: true, mode: "single", selected: selectedDate, month: monthDate, onMonthChange: handleMonthChanged, weekStartsOn: convertWeekStart(weekStart), onDayClick: handleCustomDayClick }) })) : null, _jsx("span", { className: "gd-datepicker-icon gd-icon-calendar" })
248
+ ] }));
289
249
  }
250
+ export const WrappedDatePicker = memo(WrappedDatePickerCore);
251
+ WrappedDatePicker.displayName = "WrappedDatePicker";
290
252
  const DatePickerWithIntl = injectIntl(WrappedDatePicker);
291
253
  /**
292
254
  * @internal
@@ -34,6 +34,15 @@ export interface IStylingEditorDialogProps<T extends StylingPickerItemContent> e
34
34
  * submission and display it, or undefined when the content is valid.
35
35
  */
36
36
  validateDefinition?: (content: T) => string | undefined;
37
+ /**
38
+ * Optional non-blocking check of the parsed definition content.
39
+ *
40
+ * @remarks
41
+ * Called only when the definition is valid JSON. Return a localized message to display it as a
42
+ * warning, or undefined when there is nothing to report. Unlike {@link IStylingEditorDialogProps.validateDefinition}
43
+ * this never prevents submission — use it for content that applies but degrades the result.
44
+ */
45
+ validateDefinitionWarning?: (content: T) => string | undefined;
37
46
  }
38
47
  /**
39
48
  * @internal
@@ -1 +1 @@
1
- {"version":3,"file":"StylingEditorDialog.d.ts","sourceRoot":"","sources":["../../../src/Dialog/StylingEditorDialog/StylingEditorDialog.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAOnF,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAEH,KAAK,+BAA+B,EACvC,MAAM,gCAAgC,CAAC;AAGxC;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,aAAa,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,wBAAwB;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB,CAAC,CAAC,SAAS,wBAAwB,CACzE,SAAQ,+BAA+B,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,WAAW,CAAC;IACxF,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC;IACpC,QAAQ,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;IACnC,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,MAAM,EAAE,CAAC;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;CAC3D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,wBAAwB,EAAE,KAAK,EAAE,yBAAyB,CAAC,CAAC,CAAC,2CAM1G"}
1
+ {"version":3,"file":"StylingEditorDialog.d.ts","sourceRoot":"","sources":["../../../src/Dialog/StylingEditorDialog/StylingEditorDialog.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAQnF,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAEH,KAAK,+BAA+B,EACvC,MAAM,gCAAgC,CAAC;AAGxC;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,aAAa,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,wBAAwB;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB,CAAC,CAAC,SAAS,wBAAwB,CACzE,SAAQ,+BAA+B,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,WAAW,CAAC;IACxF,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC;IACpC,QAAQ,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;IACnC,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,MAAM,EAAE,CAAC;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD;;;;;;;OAOG;IACH,yBAAyB,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;CAClE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,wBAAwB,EAAE,KAAK,EAAE,yBAAyB,CAAC,CAAC,CAAC,2CAM1G"}
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // (C) 2022-2026 GoodData Corporation
3
- import { useMemo, useState } from "react";
3
+ import { useCallback, useMemo, useState } from "react";
4
4
  import cx from "classnames";
5
5
  import { useIntl } from "react-intl";
6
6
  import { IntlWrapper } from "@gooddata/sdk-ui";
7
7
  import { Button } from "../../Button/Button.js";
8
+ import { Message } from "../../Messages/Message.js";
8
9
  import { Typography } from "../../Typography/Typography.js";
9
10
  import { useId } from "../../utils/useId.js";
10
11
  import { Dialog } from "../Dialog.js";
@@ -17,49 +18,48 @@ import { StylingExample } from "./StylingExample.js";
17
18
  export function StylingEditorDialog(props) {
18
19
  return (_jsx(IntlWrapper, { locale: props.locale, children: _jsx(StylingEditorDialogCore, { ...props }) }));
19
20
  }
20
- function StylingEditorDialogCore({ title, tooltip, link, stylingItem, examples, exampleToColorPreview, onClose, onSubmit, onCancel, disableSubmit, showProgressIndicator, showBackButton, onHelpClick, onExit = () => { }, className, onInvalidDefinition = () => { }, validateDefinition, }) {
21
+ function StylingEditorDialogCore({ title, tooltip, link, stylingItem, examples, exampleToColorPreview, onClose, onSubmit, onCancel, disableSubmit, showProgressIndicator, showBackButton, onHelpClick, onExit = () => { }, className, onInvalidDefinition = () => { }, validateDefinition, validateDefinitionWarning, }) {
21
22
  const intl = useIntl();
22
23
  const providedExamples = !!examples && examples.length !== 0 && !!exampleToColorPreview;
23
24
  const initialNameField = stylingItem?.name ?? "";
24
25
  const initialDefinitionField = stylingItem?.content ? JSON.stringify(stylingItem?.content, null, 4) : "";
25
26
  const [nameField, setNameField] = useState(initialNameField);
26
27
  const [definitionField, setDefinitionField] = useState(initialDefinitionField);
27
- const fieldsChanged = useMemo(() => {
28
+ const parsedDefinition = useMemo(() => {
28
29
  try {
29
- const parsedDefinition = JSON.parse(definitionField);
30
- const formattedDefinition = JSON.stringify(parsedDefinition, null, 4);
31
- return nameField !== initialNameField || formattedDefinition !== initialDefinitionField;
30
+ return { ok: true, content: JSON.parse(definitionField) };
32
31
  }
33
32
  catch {
33
+ return { ok: false };
34
+ }
35
+ }, [definitionField]);
36
+ const fieldsChanged = useMemo(() => {
37
+ if (!parsedDefinition.ok) {
34
38
  // initial state of the fields is presumed to be valid,
35
39
  // so if JSON throws error, definition was changed
36
40
  return true;
37
41
  }
38
- }, [nameField, initialNameField, definitionField, initialDefinitionField]);
42
+ const formattedDefinition = JSON.stringify(parsedDefinition.content, null, 4);
43
+ return nameField !== initialNameField || formattedDefinition !== initialDefinitionField;
44
+ }, [parsedDefinition, nameField, initialNameField, initialDefinitionField]);
39
45
  const validName = useMemo(() => nameField !== "", [nameField]);
40
- const validDefinition = useMemo(() => {
41
- try {
42
- JSON.parse(definitionField);
43
- return true;
44
- }
45
- catch {
46
- return false;
47
- }
48
- }, [definitionField]);
49
- // Content-level validation (e.g. theme color values) runs only on well-formed JSON.
50
- const definitionContentError = useMemo(() => {
51
- if (!validateDefinition || !validDefinition) {
46
+ const validDefinition = parsedDefinition.ok;
47
+ // A callback that throws must not crash the dialog, so it degrades to "nothing to report" and
48
+ // the user can keep working.
49
+ const runContentCheck = useCallback((check, name) => {
50
+ if (!check || !parsedDefinition.ok) {
52
51
  return undefined;
53
52
  }
54
53
  try {
55
- return validateDefinition(JSON.parse(definitionField));
54
+ return check(parsedDefinition.content);
56
55
  }
57
56
  catch (error) {
58
- // a buggy validator must not crash the dialog; degrade gracefully and let the user proceed
59
- console.error("StylingEditorDialog: the provided validateDefinition callback threw.", error);
57
+ console.error(`StylingEditorDialog: the provided ${name} callback threw.`, error);
60
58
  return undefined;
61
59
  }
62
- }, [validateDefinition, validDefinition, definitionField]);
60
+ }, [parsedDefinition]);
61
+ const definitionContentError = useMemo(() => runContentCheck(validateDefinition, "validateDefinition"), [runContentCheck, validateDefinition]);
62
+ const definitionContentWarning = useMemo(() => runContentCheck(validateDefinitionWarning, "validateDefinitionWarning"), [runContentCheck, validateDefinitionWarning]);
63
63
  const validFields = useMemo(() => validName && validDefinition && !definitionContentError, [validName, validDefinition, definitionContentError]);
64
64
  const isSubmitDisabled = useMemo(() => !validFields || !fieldsChanged || disableSubmit, [validFields, fieldsChanged, disableSubmit]);
65
65
  const emptyDefinition = useMemo(() => definitionField === "", [definitionField]);
@@ -89,10 +89,10 @@ function StylingEditorDialogCore({ title, tooltip, link, stylingItem, examples,
89
89
  stylingItem?.ref,
90
90
  intl,
91
91
  ]);
92
- const getFinalStylingItem = (original, definition, name) => {
92
+ const getFinalStylingItem = (original, content, name) => {
93
93
  return {
94
94
  ...(original || {}),
95
- content: JSON.parse(definition),
95
+ content,
96
96
  name,
97
97
  };
98
98
  };
@@ -111,13 +111,18 @@ function StylingEditorDialogCore({ title, tooltip, link, stylingItem, examples,
111
111
  _jsxs("form", { className: "gd-styling-editor-dialog-content-form", onSubmit: (e) => e.preventDefault(), children: [
112
112
  _jsxs("label", { className: "gd-styling-editor-dialog-content-form-input", children: [intl.formatMessage({ id: "stylingEditor.dialog.name" }), _jsx("input", { "aria-label": "Styling item name", className: "gd-input-field s-input-field", type: "text", value: nameField, onChange: (e) => setNameField(e.target.value) })
113
113
  ] }), _jsxs("label", { className: "gd-styling-editor-dialog-content-form-textarea", children: [intl.formatMessage({ id: "stylingEditor.dialog.definition" }), _jsx("textarea", { "aria-label": "Styling item definition", className: "gd-input-field s-textarea-field", wrap: "off", value: definitionField, onChange: (e) => setDefinitionField(e.target.value) })
114
- ] })
115
- ] }), providedExamples ? (_jsxs("div", { className: cx("gd-styling-editor-dialog-content-examples", "s-gd-styling-editor-dialog-content-examples"), children: [
114
+ ] }), definitionContentWarning && !definitionContentError ? (_jsx(Message, { type: "warning", className: "gd-styling-editor-dialog-content-form-warning s-styling-editor-warning", children: definitionContentWarning })) : null] }), providedExamples ? (_jsxs("div", { className: cx("gd-styling-editor-dialog-content-examples", "s-gd-styling-editor-dialog-content-examples"), children: [
116
115
  _jsx(BubbleHeaderSeparator, { title: intl.formatMessage({ id: "stylingEditor.dialog.examples" }), message: tooltip }), _jsx("div", { className: "gd-styling-editor-dialog-content-examples-list", children: examples.map((example, index) => (_jsx(StylingExample, { name: example.name ?? "", colors: exampleToColorPreview(example.content), onClick: () => {
117
116
  setNameField(example.name ?? "");
118
117
  setDefinitionField(JSON.stringify(example.content, null, 4));
119
118
  } }, index))) })
120
- ] })) : null] }), _jsx(StylingEditorDialogFooter, { disableSubmit: isSubmitDisabled, showProgressIndicator: showProgressIndicator, link: link, errorMessage: errorMessage, onSubmit: () => onSubmit?.(getFinalStylingItem(stylingItem, definitionField, nameField)), onCancel: () => {
119
+ ] })) : null] }), _jsx(StylingEditorDialogFooter, { disableSubmit: isSubmitDisabled, showProgressIndicator: showProgressIndicator, link: link, errorMessage: errorMessage, onSubmit: () => {
120
+ // Submission is disabled while the definition does not parse, so this only
121
+ // narrows the type rather than guarding a reachable state.
122
+ if (parsedDefinition.ok) {
123
+ onSubmit?.(getFinalStylingItem(stylingItem, parsedDefinition.content, nameField));
124
+ }
125
+ }, onCancel: () => {
121
126
  onExit?.(nameField, definitionField);
122
127
  onCancel?.();
123
128
  }, onHelpClick: onHelpClick })
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownButton.d.ts","sourceRoot":"","sources":["../../src/Dropdown/DropdownButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AAMvF,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAE5E;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,wBAAwB,GAAG;QAAE,SAAS,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,CAAA;KAAE,CAAC;IAEjG,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAElC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAC3B,EAAE,EACF,SAAS,EACT,mBAAmB,EAEnB,KAAK,EACL,KAAa,EACb,QAAQ,EAER,MAAM,EACN,OAAc,EACd,QAAQ,EACR,WAAmB,EACnB,OAAO,EACP,QAAQ,EACR,UAAU,EACV,SAAS,EACZ,EAAE,oBAAoB,2CAuDtB"}
1
+ {"version":3,"file":"DropdownButton.d.ts","sourceRoot":"","sources":["../../src/Dropdown/DropdownButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AAMvF,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAE5E;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,wBAAwB,GAAG;QAAE,SAAS,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,CAAA;KAAE,CAAC;IAEjG,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAElC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAC3B,EAAE,EACF,SAAS,EACT,mBAAmB,EAEnB,KAAK,EACL,KAAa,EACb,QAAQ,EAER,MAAM,EACN,OAAc,EACd,QAAQ,EACR,WAAmB,EACnB,OAAO,EACP,QAAQ,EACR,UAAU,EACV,SAAS,EACZ,EAAE,oBAAoB,2CA4DtB"}
@@ -29,6 +29,11 @@ export function DropdownButton({ id, className, accessibilityConfig, value, titl
29
29
  ariaLabel,
30
30
  ariaLabelledBy,
31
31
  ariaDescribedBy,
32
+ // Without a dropdownId the popup id is unknown, but the trigger is still a combobox,
33
+ // so it has to expose its expanded state - callers commonly pass only isOpen - along
34
+ // with any explicitly controlled id.
35
+ ariaExpanded: effectiveAriaExpanded,
36
+ ariaControls,
32
37
  role: role ?? "combobox",
33
38
  });
34
39
  return (_jsx(Button, { id: id, accessibilityConfig: buttonAccessibilityConfig, title: title && typeof title === "string" ? title : undefined, className: buttonClasses, value: value, iconLeft: iconLeft, iconRight: isOpen ? "gd-icon-navigateup" : "gd-icon-navigatedown", disabled: disabled, onClick: onClick, ref: buttonRef, children: children }));
@@ -1,7 +1,6 @@
1
1
  import { type ChangeEvent } from "react";
2
2
  import { type IInputPureProps } from "./InputPure.js";
3
3
  import { type Separators } from "./typings.js";
4
- export declare const MAX_NUMBER: number;
5
4
  /**
6
5
  * @internal
7
6
  */
@@ -1 +1 @@
1
- {"version":3,"file":"InputWithNumberFormat.d.ts","sourceRoot":"","sources":["../../src/Form/InputWithNumberFormat.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,WAAW,EAAsD,MAAM,OAAO,CAAC;AAI7F,OAAO,EAAE,KAAK,eAAe,EAAa,MAAM,gBAAgB,CAAC;AAEjE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/C,eAAO,MAAM,UAAU,QAAW,CAAC;AA6DnC;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC3C,UAAU,CAAC,EAAE,UAAU,CAAC;CAC3B;AAED;;GAEG;AAEH,MAAM,WAAW,2BAA2B;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AAEH,MAAM,WAAW,2BACb,SAAQ,8BAA8B,EAAE,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC;IACzE,qGAAqG;IACrG,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;CAChF;AAUD;;GAEG;AACH,eAAO,MAAM,qBAAqB,mEAsEhC,CAAC"}
1
+ {"version":3,"file":"InputWithNumberFormat.d.ts","sourceRoot":"","sources":["../../src/Form/InputWithNumberFormat.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,WAAW,EAAsD,MAAM,OAAO,CAAC;AAI7F,OAAO,EAAE,KAAK,eAAe,EAAa,MAAM,gBAAgB,CAAC;AAEjE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAmD/C;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC3C,UAAU,CAAC,EAAE,UAAU,CAAC;CAC3B;AAED;;GAEG;AAEH,MAAM,WAAW,2BAA2B;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AAEH,MAAM,WAAW,2BACb,SAAQ,8BAA8B,EAAE,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC;IACzE,qGAAqG;IACrG,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;CAChF;AAUD;;GAEG;AACH,eAAO,MAAM,qBAAqB,mEAsEhC,CAAC"}
@@ -4,20 +4,9 @@ import { memo, useEffect, useRef, useState } from "react";
4
4
  import { memoize } from "lodash-es";
5
5
  import { InputPure } from "./InputPure.js";
6
6
  import { DEFAULT_SEPARATORS, formatNumberWithSeparators } from "./numberFormat.js";
7
- // Highest number (BIGINT) according to gooddata documentation help.gooddata.com object-datatypes
8
- export const MAX_NUMBER = 10 ** 15;
9
- // Max number of digits right to decimal point according to gooddata documentation help.gooddata.com object-datatypes
10
- const MAX_DECIMAL_POINT_NUMBERS = 6;
11
7
  const getDanglingDecimalPointRegExp = memoize((decimal) => new RegExp(`\\${decimal}$`));
12
- const getFormatValidationRegExp = memoize(({ thousand, decimal }) => new RegExp(`^-?(\\d|\\${thousand})*(\\${decimal}\\d*)?$`));
13
- const parseStandardNumberString = (numberString) => {
14
- const belowDecimal = numberString.split(".")[1];
15
- const roundedNumberString = belowDecimal && belowDecimal.length >= MAX_DECIMAL_POINT_NUMBERS
16
- ? parseFloat(numberString).toFixed(MAX_DECIMAL_POINT_NUMBERS)
17
- : numberString;
18
- const number = parseFloat(roundedNumberString);
19
- return number === 0 ? 0 : number;
20
- };
8
+ // A half-typed exponent ("1e", "1e+") stays valid so it can be entered one character at a time.
9
+ const buildValidationRegExp = ({ thousand, decimal }) => new RegExp(`^-?(\\d|\\${thousand})*(\\${decimal}\\d*)?([eE][-+]?\\d*)?$`);
21
10
  // Removes thousand separators while keeping the decimal separator intact, so the value can be
22
11
  // edited as plain digits (no separators popping in/out) while the input is focused.
23
12
  const removeThousandSeparators = (value, { thousand } = DEFAULT_SEPARATORS) => value.split(thousand).join("");
@@ -27,7 +16,7 @@ const convertFormattedStringToStandard = (formattedString, { thousand, decimal }
27
16
  const withStandardDecimalPoint = withoutDanglingDecimalPoint.split(decimal).join(".");
28
17
  return withStandardDecimalPoint.length > 0 ? withStandardDecimalPoint : null;
29
18
  };
30
- const parse = (value, separators = DEFAULT_SEPARATORS) => {
19
+ const parse = (value, separators) => {
31
20
  if (value === null || value === "" || value === "-") {
32
21
  return null;
33
22
  }
@@ -35,11 +24,16 @@ const parse = (value, separators = DEFAULT_SEPARATORS) => {
35
24
  if (numberString === null) {
36
25
  return null;
37
26
  }
38
- return parseStandardNumberString(numberString);
27
+ const number = parseFloat(numberString);
28
+ return number === 0 ? 0 : number;
39
29
  };
40
- const isValid = (value, separators = DEFAULT_SEPARATORS) => {
30
+ // The wire layer draws the same line: it throws on Infinity/NaN.
31
+ const isValid = (value, separators) => {
32
+ if (!buildValidationRegExp(separators).test(value)) {
33
+ return false;
34
+ }
41
35
  const parsed = parse(value, separators);
42
- return getFormatValidationRegExp(separators).test(value) && Math.abs(parsed ?? 0) <= MAX_NUMBER;
36
+ return parsed === null || Number.isFinite(parsed);
43
37
  };
44
38
  // Coerces input value to number for formatting (handles string/number/null/undefined)
45
39
  const toNumberValue = (value) => {
@@ -51,7 +45,7 @@ const toNumberValue = (value) => {
51
45
  /**
52
46
  * @internal
53
47
  */
54
- export const InputWithNumberFormat = memo(function InputWithNumberFormat({ separators, value: propValue, onChange, onFocus, onBlur, ...restProps }) {
48
+ export const InputWithNumberFormat = memo(function InputWithNumberFormat({ separators = DEFAULT_SEPARATORS, value: propValue, onChange, onFocus, onBlur, ...restProps }) {
55
49
  const inputRef = useRef(null);
56
50
  const [value, setValue] = useState(() => formatNumberWithSeparators(toNumberValue(propValue), separators));
57
51
  const [isFocused, setIsFocused] = useState(false);
@@ -1 +1 @@
1
- {"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../src/Header/Header.tsx"],"names":[],"mappings":"AA2BA,OAAO,EAAE,KAAK,eAAe,EAA8C,MAAM,cAAc,CAAC;AAyBhG;;GAEG;AACH,eAAO,MAAM,SAAS,kGAokBpB,CAAC"}
1
+ {"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../src/Header/Header.tsx"],"names":[],"mappings":"AA2BA,OAAO,EAAE,KAAK,eAAe,EAA8C,MAAM,cAAc,CAAC;AAyBhG;;GAEG;AACH,eAAO,MAAM,SAAS,kGAskBpB,CAAC"}