@gooddata/sdk-ui-kit 11.52.0-alpha.5 → 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.
- package/esm/@ui/UiCombobox/UiComboboxInput.d.ts +2 -0
- package/esm/@ui/UiCombobox/UiComboboxInput.d.ts.map +1 -1
- package/esm/@ui/UiCombobox/UiComboboxInput.js +2 -2
- package/esm/@ui/UiMenu/components/defaults/DefaultUiMenuInteractiveItem.d.ts.map +1 -1
- package/esm/@ui/UiMenu/components/defaults/DefaultUiMenuInteractiveItem.js +25 -22
- package/esm/@ui/UiMenu/types.d.ts +1 -0
- package/esm/@ui/UiMenu/types.d.ts.map +1 -1
- package/esm/@ui/UiTextInput/UiTextInput.d.ts +3 -1
- package/esm/@ui/UiTextInput/UiTextInput.d.ts.map +1 -1
- package/esm/@ui/UiTextInput/UiTextInput.js +2 -2
- package/esm/Datepicker/Datepicker.d.ts +3 -52
- package/esm/Datepicker/Datepicker.d.ts.map +1 -1
- package/esm/Datepicker/Datepicker.js +142 -180
- package/esm/Form/InputWithNumberFormat.d.ts +0 -1
- package/esm/Form/InputWithNumberFormat.d.ts.map +1 -1
- package/esm/Form/InputWithNumberFormat.js +12 -18
- package/esm/sdk-ui-kit.d.ts +6 -1
- package/package.json +11 -11
- package/src/@ui/UiMenu/UiMenu.scss +1 -1
- package/src/@ui/UiTextInput/UiTextInput.scss +7 -0
- package/styles/css/main.css +4 -1
- package/styles/css/main.css.map +1 -1
|
@@ -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,
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
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,
|
|
182
|
-
|
|
183
|
-
|
|
173
|
+
const parsedDate = parseDate(value, dateFormat);
|
|
174
|
+
onValidateInput?.(value);
|
|
175
|
+
setInputValue(value);
|
|
184
176
|
if (parsedDate) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|
-
|
|
193
|
+
setIsOpen(false);
|
|
214
194
|
return;
|
|
215
195
|
}
|
|
216
|
-
if (
|
|
217
|
-
|
|
196
|
+
if (selectedDate && isSameDay(selectedDate, newlySelectedDate)) {
|
|
197
|
+
setIsOpen(false);
|
|
218
198
|
return;
|
|
219
199
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
238
|
-
};
|
|
239
|
-
|
|
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
|
-
|
|
218
|
+
setIsOpen(false);
|
|
263
219
|
}
|
|
264
220
|
if (isEnterKey(e)) {
|
|
265
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|
@@ -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;
|
|
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
|
-
|
|
13
|
-
const
|
|
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
|
|
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
|
-
|
|
27
|
+
const number = parseFloat(numberString);
|
|
28
|
+
return number === 0 ? 0 : number;
|
|
39
29
|
};
|
|
40
|
-
|
|
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
|
|
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);
|
package/esm/sdk-ui-kit.d.ts
CHANGED
|
@@ -7017,6 +7017,8 @@ export declare interface IUiComboboxInputProps {
|
|
|
7017
7017
|
accessibilityConfig?: IAccessibilityConfigBase;
|
|
7018
7018
|
/** Visible placeholder. */
|
|
7019
7019
|
placeholder?: string;
|
|
7020
|
+
/** Marks the field invalid: error-colored border plus `aria-invalid`. */
|
|
7021
|
+
isError?: boolean;
|
|
7020
7022
|
/** Form field name forwarded to the underlying input. */
|
|
7021
7023
|
name?: string;
|
|
7022
7024
|
autoFocus?: boolean;
|
|
@@ -8116,6 +8118,7 @@ export declare type IUiMenuInteractiveItem<T extends IUiMenuItemData = object> =
|
|
|
8116
8118
|
stringTitle: string;
|
|
8117
8119
|
isDisabled?: boolean;
|
|
8118
8120
|
isSelected?: boolean;
|
|
8121
|
+
selectionRole?: "radio" | "checkbox";
|
|
8119
8122
|
isDestructive?: boolean;
|
|
8120
8123
|
tooltip?: ReactNode;
|
|
8121
8124
|
tooltipWidth?: number;
|
|
@@ -9004,6 +9007,8 @@ export declare interface IUiTextInputProps {
|
|
|
9004
9007
|
onIconAfter?: IUiTextInputIconAfterButton;
|
|
9005
9008
|
/** Accessibility config forwarded to the input element. */
|
|
9006
9009
|
accessibilityConfig?: IAccessibilityConfigBase;
|
|
9010
|
+
/** Marks the field invalid: error-colored border plus `aria-invalid`. */
|
|
9011
|
+
isError?: boolean;
|
|
9007
9012
|
disabled?: boolean;
|
|
9008
9013
|
autoFocus?: boolean;
|
|
9009
9014
|
/** Forwarded to the input element. Use for autocomplete / combobox patterns. */
|
|
@@ -11374,7 +11379,7 @@ export declare function UiTags({ tags, tagOptions, addLabel, nameLabel, cancelLa
|
|
|
11374
11379
|
*
|
|
11375
11380
|
* @internal
|
|
11376
11381
|
*/
|
|
11377
|
-
export declare function UiTextInput({ type, value, onChange, label, placeholder, iconBefore, iconAfter, onIconAfter, accessibilityConfig, disabled, autoFocus, onKeyDown, onFocus, onBlur, onClick, dataTestId, inputRef, wrapperRef, name, autoComplete, autoCapitalize, autoCorrect }: IUiTextInputProps): JSX.Element;
|
|
11382
|
+
export declare function UiTextInput({ type, value, onChange, label, placeholder, iconBefore, iconAfter, onIconAfter, accessibilityConfig, isError, disabled, autoFocus, onKeyDown, onFocus, onBlur, onClick, dataTestId, inputRef, wrapperRef, name, autoComplete, autoCapitalize, autoCorrect }: IUiTextInputProps): JSX.Element;
|
|
11378
11383
|
|
|
11379
11384
|
/**
|
|
11380
11385
|
* Interpolation values accepted by `react-intl`'s `formatMessage`, **narrowed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gooddata/sdk-ui-kit",
|
|
3
|
-
"version": "11.52.0-alpha.
|
|
3
|
+
"version": "11.52.0-alpha.6",
|
|
4
4
|
"description": "GoodData SDK - UI Building Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "GoodData Corporation",
|
|
@@ -76,11 +76,11 @@
|
|
|
76
76
|
"tslib": "2.8.1",
|
|
77
77
|
"unified": "^11.0.5",
|
|
78
78
|
"uuid": "11.1.1",
|
|
79
|
-
"@gooddata/sdk-backend-spi": "11.52.0-alpha.
|
|
80
|
-
"@gooddata/sdk-
|
|
81
|
-
"@gooddata/sdk-
|
|
82
|
-
"@gooddata/sdk-ui
|
|
83
|
-
"@gooddata/util": "11.52.0-alpha.
|
|
79
|
+
"@gooddata/sdk-backend-spi": "11.52.0-alpha.6",
|
|
80
|
+
"@gooddata/sdk-model": "11.52.0-alpha.6",
|
|
81
|
+
"@gooddata/sdk-ui-theme-provider": "11.52.0-alpha.6",
|
|
82
|
+
"@gooddata/sdk-ui": "11.52.0-alpha.6",
|
|
83
|
+
"@gooddata/util": "11.52.0-alpha.6"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@microsoft/api-documenter": "^7.17.0",
|
|
@@ -129,11 +129,11 @@
|
|
|
129
129
|
"typescript": "5.9.3",
|
|
130
130
|
"vitest": "4.1.8",
|
|
131
131
|
"vitest-dom": "0.1.1",
|
|
132
|
-
"@gooddata/
|
|
133
|
-
"@gooddata/
|
|
134
|
-
"@gooddata/reference-workspace": "11.52.0-alpha.
|
|
135
|
-
"@gooddata/
|
|
136
|
-
"@gooddata/
|
|
132
|
+
"@gooddata/eslint-config": "11.52.0-alpha.6",
|
|
133
|
+
"@gooddata/oxlint-config": "11.52.0-alpha.6",
|
|
134
|
+
"@gooddata/reference-workspace": "11.52.0-alpha.6",
|
|
135
|
+
"@gooddata/sdk-backend-mockingbird": "11.52.0-alpha.6",
|
|
136
|
+
"@gooddata/stylelint-config": "11.52.0-alpha.6"
|
|
137
137
|
},
|
|
138
138
|
"peerDependencies": {
|
|
139
139
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -29,6 +29,13 @@
|
|
|
29
29
|
background-color: var(--gd-palette-complementary-2);
|
|
30
30
|
cursor: not-allowed;
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
// Repeated with :focus-within to outweigh the focus rule above, so the border stays error-colored
|
|
34
|
+
// while the field is being corrected.
|
|
35
|
+
&--error,
|
|
36
|
+
&--error:focus-within {
|
|
37
|
+
border-color: var(--gd-palette-error-base);
|
|
38
|
+
}
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
&__icon-before {
|
package/styles/css/main.css
CHANGED
|
@@ -1958,7 +1958,7 @@
|
|
|
1958
1958
|
outline: auto 5px Highlight; /* For Firefox */
|
|
1959
1959
|
outline: auto 5px -webkit-focus-ring-color; /* For Chrome */
|
|
1960
1960
|
}
|
|
1961
|
-
.gd-ui-kit-menu__item-wrapper .gd-ui-kit-tooltip__anchor {
|
|
1961
|
+
.gd-ui-kit-menu__item-wrapper .gd-ui-kit-tooltip__anchor:not(.gd-ui-kit-tooltip__anchor--inline) {
|
|
1962
1962
|
width: 100%;
|
|
1963
1963
|
}
|
|
1964
1964
|
.gd-ui-kit-menu__item {
|
|
@@ -3608,6 +3608,9 @@
|
|
|
3608
3608
|
background-color: var(--gd-palette-complementary-2);
|
|
3609
3609
|
cursor: not-allowed;
|
|
3610
3610
|
}
|
|
3611
|
+
.gd-ui-kit-text-input__field--error, .gd-ui-kit-text-input__field--error:focus-within {
|
|
3612
|
+
border-color: var(--gd-palette-error-base);
|
|
3613
|
+
}
|
|
3611
3614
|
.gd-ui-kit-text-input__icon-before {
|
|
3612
3615
|
display: inline-flex;
|
|
3613
3616
|
align-items: center;
|