xmlui 0.10.24 → 0.10.25
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/lib/{index-DoIVkz5T.mjs → index-CCEPGw_x.mjs} +476 -477
- package/dist/lib/index.css +1 -1
- package/dist/lib/{initMock-CSGEd746.mjs → initMock-DFcCR7ey.mjs} +1 -1
- package/dist/lib/xmlui.d.ts +1 -0
- package/dist/lib/xmlui.mjs +1 -1
- package/dist/metadata/{collectedComponentMetadata-B3Hs8_cV.mjs → collectedComponentMetadata-mwkNkxN_.mjs} +472 -473
- package/dist/metadata/{initMock-DQrGwkya.mjs → initMock-BVxHA6wu.mjs} +1 -1
- package/dist/metadata/style.css +1 -1
- package/dist/metadata/xmlui-metadata.mjs +1 -1
- package/dist/metadata/xmlui-metadata.umd.js +3 -3
- package/dist/scripts/package.json +1 -1
- package/dist/scripts/src/components/AppHeader/AppHeaderNative.js +2 -1
- package/dist/scripts/src/components/AutoComplete/AutoComplete.spec.js +1 -1
- package/dist/scripts/src/components/AutoComplete/AutoCompleteNative.js +30 -5
- package/dist/scripts/src/components/Form/Form.spec.js +2 -2
- package/dist/scripts/src/components/FormItem/FormItemNative.js +5 -1
- package/dist/scripts/src/components/ModalDialog/ModalDialog.js +4 -6
- package/dist/scripts/src/components/ModalDialog/ModalDialog.spec.js +19 -0
- package/dist/scripts/src/components/NavGroup/NavGroup.spec.js +103 -11
- package/dist/scripts/src/components/NavGroup/NavGroupNative.js +6 -1
- package/dist/scripts/src/components/Option/Option.spec.js +3 -1
- package/dist/scripts/src/components/Select/HiddenOption.js +3 -3
- package/dist/scripts/src/components/Select/Select.js +2 -3
- package/dist/scripts/src/components/Select/Select.spec.js +4 -6
- package/dist/scripts/src/components/Select/SelectNative.js +187 -47
- package/dist/scripts/src/components-core/rendering/ComponentAdapter.js +11 -0
- package/dist/scripts/src/components-core/rendering/Container.js +3 -4
- package/dist/scripts/src/components-core/rendering/StateContainer.js +16 -18
- package/dist/scripts/src/components-core/rendering/reducer.js +6 -3
- package/dist/scripts/src/testing/ComponentDrivers.js +1 -1
- package/dist/standalone/xmlui-standalone.es.d.ts +1 -0
- package/dist/standalone/xmlui-standalone.umd.js +18 -18
- package/package.json +1 -1
- package/dist/scripts/src/components/Select/MultiSelectOption.js +0 -42
- package/dist/scripts/src/components/Select/SelectOption.js +0 -34
- package/dist/scripts/src/components/Select/SimpleSelect.js +0 -57
|
@@ -15,15 +15,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.Select = exports.defaultProps = void 0;
|
|
18
|
-
const react_1 = require("react");
|
|
19
18
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
20
|
-
const
|
|
21
|
-
const react_compose_refs_1 = require("@radix-ui/react-compose-refs");
|
|
19
|
+
const react_1 = require("react");
|
|
22
20
|
const react_popover_1 = require("@radix-ui/react-popover");
|
|
23
21
|
const classnames_1 = __importDefault(require("classnames"));
|
|
24
|
-
const react_focus_scope_1 = require("@radix-ui/react-focus-scope");
|
|
25
|
-
const SelectOption_1 = require("./SelectOption");
|
|
26
22
|
const Select_module_scss_1 = __importDefault(require("./Select.module.scss"));
|
|
23
|
+
const react_compose_refs_1 = require("@radix-ui/react-compose-refs");
|
|
27
24
|
const constants_1 = require("../../components-core/constants");
|
|
28
25
|
const ThemeContext_1 = require("../../components-core/theming/ThemeContext");
|
|
29
26
|
const misc_1 = require("../../components-core/utils/misc");
|
|
@@ -32,9 +29,7 @@ const SelectContext_1 = require("./SelectContext");
|
|
|
32
29
|
const OptionTypeProvider_1 = __importDefault(require("../Option/OptionTypeProvider"));
|
|
33
30
|
const OptionContext_1 = require("./OptionContext");
|
|
34
31
|
const HiddenOption_1 = require("./HiddenOption");
|
|
35
|
-
const
|
|
36
|
-
const SimpleSelect_1 = require("./SimpleSelect");
|
|
37
|
-
const MultiSelectOption_1 = require("./MultiSelectOption");
|
|
32
|
+
const PART_LIST_WRAPPER = "listWrapper";
|
|
38
33
|
exports.defaultProps = {
|
|
39
34
|
enabled: true,
|
|
40
35
|
placeholder: "",
|
|
@@ -80,7 +75,7 @@ const SelectTriggerActions = ({ value, multiSelect, enabled, readOnly, clearValu
|
|
|
80
75
|
clearValue();
|
|
81
76
|
}, children: (0, jsx_runtime_1.jsx)(IconNative_1.default, { name: "close" }) })), showChevron && ((0, jsx_runtime_1.jsx)("span", { className: Select_module_scss_1.default.action, children: (0, jsx_runtime_1.jsx)(IconNative_1.default, { name: "chevrondown" }) }))] }));
|
|
82
77
|
};
|
|
83
|
-
exports.Select = (0,
|
|
78
|
+
exports.Select = (0, react_1.forwardRef)(function Select(_a, forwardedRef) {
|
|
84
79
|
var {
|
|
85
80
|
// Basic props
|
|
86
81
|
id, initialValue, value, enabled = exports.defaultProps.enabled, placeholder = exports.defaultProps.placeholder, autoFocus = exports.defaultProps.autoFocus, readOnly = false, required = exports.defaultProps.required,
|
|
@@ -96,17 +91,16 @@ exports.Select = (0, react_2.forwardRef)(function Select(_a, forwardedRef) {
|
|
|
96
91
|
inProgress = exports.defaultProps.inProgress, inProgressNotificationMessage = exports.defaultProps.inProgressNotificationMessage,
|
|
97
92
|
// Internal
|
|
98
93
|
updateState = constants_1.noop, registerComponentApi, children } = _a, rest = __rest(_a, ["id", "initialValue", "value", "enabled", "placeholder", "autoFocus", "readOnly", "required", "style", "className", "dropdownHeight", "validationStatus", "onDidChange", "onFocus", "onBlur", "searchable", "multiSelect", "emptyListTemplate", "valueRenderer", "optionRenderer", "inProgress", "inProgressNotificationMessage", "updateState", "registerComponentApi", "children"]);
|
|
99
|
-
const [referenceElement, setReferenceElement] = (0,
|
|
100
|
-
const [open, setOpen] = (0,
|
|
101
|
-
const [width, setWidth] = (0,
|
|
102
|
-
const observer = (0,
|
|
94
|
+
const [referenceElement, setReferenceElement] = (0, react_1.useState)(null);
|
|
95
|
+
const [open, setOpen] = (0, react_1.useState)(false);
|
|
96
|
+
const [width, setWidth] = (0, react_1.useState)(0);
|
|
97
|
+
const observer = (0, react_1.useRef)();
|
|
103
98
|
const { root } = (0, ThemeContext_1.useTheme)();
|
|
104
|
-
const [options, setOptions] = (0,
|
|
105
|
-
const
|
|
106
|
-
const [
|
|
107
|
-
const [selectedIndex, setSelectedIndex] = (0, react_2.useState)(-1);
|
|
99
|
+
const [options, setOptions] = (0, react_1.useState)(new Set());
|
|
100
|
+
const [searchTerm, setSearchTerm] = (0, react_1.useState)("");
|
|
101
|
+
const [selectedIndex, setSelectedIndex] = (0, react_1.useState)(-1);
|
|
108
102
|
// Filter options based on search term
|
|
109
|
-
const filteredOptions = (0,
|
|
103
|
+
const filteredOptions = (0, react_1.useMemo)(() => {
|
|
110
104
|
if (!searchTerm || searchTerm.trim() === "") {
|
|
111
105
|
return Array.from(options);
|
|
112
106
|
}
|
|
@@ -117,20 +111,20 @@ exports.Select = (0, react_2.forwardRef)(function Select(_a, forwardedRef) {
|
|
|
117
111
|
});
|
|
118
112
|
}, [options, searchTerm]);
|
|
119
113
|
// Reset selected index when options change or dropdown closes
|
|
120
|
-
(0,
|
|
114
|
+
(0, react_1.useEffect)(() => {
|
|
121
115
|
if (!open) {
|
|
122
116
|
setSelectedIndex(-1);
|
|
123
117
|
setSearchTerm("");
|
|
124
118
|
}
|
|
125
119
|
}, [open]);
|
|
126
120
|
// Set initial state based on the initialValue prop
|
|
127
|
-
(0,
|
|
121
|
+
(0, react_1.useEffect)(() => {
|
|
128
122
|
if (initialValue !== undefined) {
|
|
129
123
|
updateState({ value: initialValue }, { initial: true });
|
|
130
124
|
}
|
|
131
125
|
}, [initialValue, updateState]);
|
|
132
126
|
// Observe the size of the reference element
|
|
133
|
-
(0,
|
|
127
|
+
(0, react_1.useEffect)(() => {
|
|
134
128
|
var _a;
|
|
135
129
|
const current = referenceElement;
|
|
136
130
|
(_a = observer.current) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
@@ -144,7 +138,7 @@ exports.Select = (0, react_2.forwardRef)(function Select(_a, forwardedRef) {
|
|
|
144
138
|
};
|
|
145
139
|
}, [referenceElement]);
|
|
146
140
|
// Handle option selection
|
|
147
|
-
const toggleOption = (0,
|
|
141
|
+
const toggleOption = (0, react_1.useCallback)((selectedValue) => {
|
|
148
142
|
const newSelectedValue = multiSelect
|
|
149
143
|
? Array.isArray(value)
|
|
150
144
|
? value.map((v) => String(v)).includes(String(selectedValue))
|
|
@@ -161,46 +155,50 @@ exports.Select = (0, react_2.forwardRef)(function Select(_a, forwardedRef) {
|
|
|
161
155
|
}
|
|
162
156
|
}, [multiSelect, value, updateState, onDidChange]);
|
|
163
157
|
// Clear selected value
|
|
164
|
-
const clearValue = (0,
|
|
158
|
+
const clearValue = (0, react_1.useCallback)(() => {
|
|
165
159
|
const newValue = multiSelect ? [] : "";
|
|
166
160
|
updateState({ value: newValue });
|
|
167
161
|
onDidChange(newValue);
|
|
168
162
|
}, [multiSelect, updateState, onDidChange]);
|
|
169
163
|
// Helper functions to find next/previous enabled option
|
|
170
|
-
const findNextEnabledIndex = (0,
|
|
164
|
+
const findNextEnabledIndex = (0, react_1.useCallback)((currentIndex) => {
|
|
165
|
+
if (filteredOptions.length === 0)
|
|
166
|
+
return -1;
|
|
171
167
|
for (let i = currentIndex + 1; i < filteredOptions.length; i++) {
|
|
172
168
|
const item = filteredOptions[i];
|
|
173
|
-
if (item.enabled !== false) {
|
|
169
|
+
if (item && item.enabled !== false) {
|
|
174
170
|
return i;
|
|
175
171
|
}
|
|
176
172
|
}
|
|
177
173
|
// Wrap around to beginning
|
|
178
174
|
for (let i = 0; i <= currentIndex; i++) {
|
|
179
175
|
const item = filteredOptions[i];
|
|
180
|
-
if (item.enabled !== false) {
|
|
176
|
+
if (item && item.enabled !== false) {
|
|
181
177
|
return i;
|
|
182
178
|
}
|
|
183
179
|
}
|
|
184
180
|
return -1;
|
|
185
181
|
}, [filteredOptions]);
|
|
186
|
-
const findPreviousEnabledIndex = (0,
|
|
182
|
+
const findPreviousEnabledIndex = (0, react_1.useCallback)((currentIndex) => {
|
|
183
|
+
if (filteredOptions.length === 0)
|
|
184
|
+
return -1;
|
|
187
185
|
for (let i = currentIndex - 1; i >= 0; i--) {
|
|
188
186
|
const item = filteredOptions[i];
|
|
189
|
-
if (item.enabled !== false) {
|
|
187
|
+
if (item && item.enabled !== false) {
|
|
190
188
|
return i;
|
|
191
189
|
}
|
|
192
190
|
}
|
|
193
191
|
// Wrap around to end
|
|
194
192
|
for (let i = filteredOptions.length - 1; i >= currentIndex; i--) {
|
|
195
193
|
const item = filteredOptions[i];
|
|
196
|
-
if (item.enabled !== false) {
|
|
194
|
+
if (item && item.enabled !== false) {
|
|
197
195
|
return i;
|
|
198
196
|
}
|
|
199
197
|
}
|
|
200
198
|
return -1;
|
|
201
199
|
}, [filteredOptions]);
|
|
202
200
|
// Keyboard navigation
|
|
203
|
-
const handleKeyDown = (0,
|
|
201
|
+
const handleKeyDown = (0, react_1.useCallback)((event) => {
|
|
204
202
|
if (!open)
|
|
205
203
|
return;
|
|
206
204
|
switch (event.key) {
|
|
@@ -222,8 +220,12 @@ exports.Select = (0, react_2.forwardRef)(function Select(_a, forwardedRef) {
|
|
|
222
220
|
event.preventDefault();
|
|
223
221
|
if (selectedIndex >= 0 && selectedIndex < filteredOptions.length) {
|
|
224
222
|
const selectedItem = filteredOptions[selectedIndex];
|
|
225
|
-
if (selectedItem.enabled !== false) {
|
|
223
|
+
if (selectedItem && selectedItem.enabled !== false) {
|
|
226
224
|
toggleOption(selectedItem.value);
|
|
225
|
+
// Close dropdown after selecting in single-select mode
|
|
226
|
+
if (!multiSelect) {
|
|
227
|
+
setOpen(false);
|
|
228
|
+
}
|
|
227
229
|
}
|
|
228
230
|
}
|
|
229
231
|
break;
|
|
@@ -237,11 +239,12 @@ exports.Select = (0, react_2.forwardRef)(function Select(_a, forwardedRef) {
|
|
|
237
239
|
selectedIndex,
|
|
238
240
|
filteredOptions,
|
|
239
241
|
toggleOption,
|
|
242
|
+
multiSelect,
|
|
240
243
|
findNextEnabledIndex,
|
|
241
244
|
findPreviousEnabledIndex,
|
|
242
245
|
]);
|
|
243
246
|
// Register component API for external interactions
|
|
244
|
-
const focus = (0,
|
|
247
|
+
const focus = (0, react_1.useCallback)(() => {
|
|
245
248
|
referenceElement === null || referenceElement === void 0 ? void 0 : referenceElement.focus();
|
|
246
249
|
}, [referenceElement]);
|
|
247
250
|
const setValue = (0, misc_1.useEvent)((newValue) => {
|
|
@@ -256,7 +259,7 @@ exports.Select = (0, react_2.forwardRef)(function Select(_a, forwardedRef) {
|
|
|
256
259
|
clearValue();
|
|
257
260
|
}
|
|
258
261
|
});
|
|
259
|
-
(0,
|
|
262
|
+
(0, react_1.useEffect)(() => {
|
|
260
263
|
registerComponentApi === null || registerComponentApi === void 0 ? void 0 : registerComponentApi({
|
|
261
264
|
focus,
|
|
262
265
|
setValue,
|
|
@@ -264,37 +267,174 @@ exports.Select = (0, react_2.forwardRef)(function Select(_a, forwardedRef) {
|
|
|
264
267
|
});
|
|
265
268
|
}, [focus, registerComponentApi, setValue, reset]);
|
|
266
269
|
// Render the "empty list" message
|
|
267
|
-
const emptyListNode = (0,
|
|
268
|
-
const onOptionAdd = (0,
|
|
269
|
-
setOptions((prev) =>
|
|
270
|
+
const emptyListNode = (0, react_1.useMemo)(() => emptyListTemplate !== null && emptyListTemplate !== void 0 ? emptyListTemplate : ((0, jsx_runtime_1.jsxs)("div", { className: Select_module_scss_1.default.selectEmpty, children: [(0, jsx_runtime_1.jsx)(IconNative_1.default, { name: "noresult" }), (0, jsx_runtime_1.jsx)("span", { children: "List is empty" })] })), [emptyListTemplate]);
|
|
271
|
+
const onOptionAdd = (0, react_1.useCallback)((option) => {
|
|
272
|
+
setOptions((prev) => {
|
|
273
|
+
// Check if option with same value already exists
|
|
274
|
+
const exists = Array.from(prev).some((opt) => opt.value === option.value);
|
|
275
|
+
if (exists) {
|
|
276
|
+
return prev;
|
|
277
|
+
}
|
|
278
|
+
const newSet = new Set(prev);
|
|
279
|
+
newSet.add(option);
|
|
280
|
+
return newSet;
|
|
281
|
+
});
|
|
270
282
|
}, []);
|
|
271
|
-
const onOptionRemove = (0,
|
|
283
|
+
const onOptionRemove = (0, react_1.useCallback)((option) => {
|
|
272
284
|
setOptions((prev) => {
|
|
273
285
|
const optionsSet = new Set(prev);
|
|
274
286
|
optionsSet.delete(option);
|
|
275
287
|
return optionsSet;
|
|
276
288
|
});
|
|
277
289
|
}, []);
|
|
278
|
-
const optionContextValue = (0,
|
|
290
|
+
const optionContextValue = (0, react_1.useMemo)(() => ({
|
|
279
291
|
onOptionAdd,
|
|
280
292
|
onOptionRemove,
|
|
281
293
|
}), [onOptionAdd, onOptionRemove]);
|
|
282
|
-
const selectContextValue = (0,
|
|
294
|
+
const selectContextValue = (0, react_1.useMemo)(() => ({
|
|
283
295
|
multiSelect,
|
|
296
|
+
readOnly,
|
|
284
297
|
value,
|
|
285
298
|
onChange: toggleOption,
|
|
286
299
|
setOpen,
|
|
287
300
|
setSelectedIndex,
|
|
288
301
|
options,
|
|
302
|
+
highlightedValue: selectedIndex >= 0 &&
|
|
303
|
+
selectedIndex < filteredOptions.length &&
|
|
304
|
+
filteredOptions[selectedIndex]
|
|
305
|
+
? filteredOptions[selectedIndex].value
|
|
306
|
+
: undefined,
|
|
289
307
|
optionRenderer,
|
|
290
|
-
}), [
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
308
|
+
}), [
|
|
309
|
+
multiSelect,
|
|
310
|
+
readOnly,
|
|
311
|
+
value,
|
|
312
|
+
toggleOption,
|
|
313
|
+
options,
|
|
314
|
+
selectedIndex,
|
|
315
|
+
filteredOptions,
|
|
316
|
+
optionRenderer,
|
|
317
|
+
]);
|
|
318
|
+
return ((0, jsx_runtime_1.jsx)(SelectContext_1.SelectContext.Provider, { value: selectContextValue, children: (0, jsx_runtime_1.jsxs)(OptionContext_1.OptionContext.Provider, { value: optionContextValue, children: [(0, jsx_runtime_1.jsx)(OptionTypeProvider_1.default, { Component: VisibleSelectOption, children: (0, jsx_runtime_1.jsxs)(react_popover_1.Popover, { open: open, onOpenChange: (isOpen) => {
|
|
319
|
+
if (!enabled)
|
|
320
|
+
return;
|
|
321
|
+
setOpen(isOpen);
|
|
322
|
+
// Reset highlighted option when dropdown closes
|
|
323
|
+
setSelectedIndex(-1);
|
|
324
|
+
}, modal: false, children: [(0, jsx_runtime_1.jsxs)(react_popover_1.PopoverTrigger, Object.assign({}, rest, { ref: (0, react_compose_refs_1.composeRefs)(setReferenceElement, forwardedRef), id: id, "aria-haspopup": "listbox", style: style, onFocus: onFocus, onBlur: onBlur, disabled: !enabled, "aria-expanded": open, "data-part-id": PART_LIST_WRAPPER, className: (0, classnames_1.default)(className, Select_module_scss_1.default.selectTrigger, Select_module_scss_1.default[validationStatus], {
|
|
297
325
|
[Select_module_scss_1.default.disabled]: !enabled,
|
|
298
326
|
[Select_module_scss_1.default.multi]: multiSelect,
|
|
299
|
-
}
|
|
327
|
+
}), role: "combobox", onClick: (event) => {
|
|
328
|
+
if (!enabled)
|
|
329
|
+
return;
|
|
330
|
+
event.stopPropagation();
|
|
331
|
+
setOpen((prev) => !prev);
|
|
332
|
+
}, onKeyDown: (event) => {
|
|
333
|
+
if (!enabled || readOnly)
|
|
334
|
+
return;
|
|
335
|
+
// Handle opening dropdown with keyboard
|
|
336
|
+
if (!open &&
|
|
337
|
+
(event.key === "ArrowDown" ||
|
|
338
|
+
event.key === "ArrowUp" ||
|
|
339
|
+
event.key === " " ||
|
|
340
|
+
event.key === "Enter")) {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
setOpen(true);
|
|
343
|
+
// Set initial selectedIndex to first enabled option if options exist
|
|
344
|
+
if (filteredOptions.length > 0) {
|
|
345
|
+
const firstEnabledIndex = findNextEnabledIndex(-1);
|
|
346
|
+
setSelectedIndex(firstEnabledIndex !== -1 ? firstEnabledIndex : 0);
|
|
347
|
+
}
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
// Handle keyboard navigation when dropdown is open
|
|
351
|
+
if (open) {
|
|
352
|
+
handleKeyDown(event);
|
|
353
|
+
}
|
|
354
|
+
}, autoFocus: autoFocus, children: [(0, jsx_runtime_1.jsx)(SelectTriggerValue, { value: value, placeholder: placeholder, readOnly: readOnly, multiSelect: multiSelect, options: options, valueRenderer: valueRenderer, toggleOption: toggleOption }), (0, jsx_runtime_1.jsx)(SelectTriggerActions, { value: value, multiSelect: multiSelect, enabled: enabled, readOnly: readOnly, clearValue: clearValue })] })), open && ((0, jsx_runtime_1.jsx)(react_popover_1.Portal, { container: root, children: (0, jsx_runtime_1.jsx)(react_popover_1.PopoverContent, { style: { minWidth: width, height: dropdownHeight }, className: Select_module_scss_1.default.selectContent, onKeyDown: handleKeyDown, children: (0, jsx_runtime_1.jsxs)("div", { className: Select_module_scss_1.default.command, children: [searchable ? ((0, jsx_runtime_1.jsxs)("div", { className: Select_module_scss_1.default.commandInputContainer, children: [(0, jsx_runtime_1.jsx)(IconNative_1.default, { name: "search" }), (0, jsx_runtime_1.jsx)("input", { role: "searchbox", className: (0, classnames_1.default)(Select_module_scss_1.default.commandInput), placeholder: "Search...", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value) })] })) : ((0, jsx_runtime_1.jsx)("button", { "aria-hidden": "true", className: Select_module_scss_1.default.srOnly })), (0, jsx_runtime_1.jsx)("div", { role: "listbox", className: Select_module_scss_1.default.commandList, children: inProgress ? ((0, jsx_runtime_1.jsx)("div", { className: Select_module_scss_1.default.loading, children: inProgressNotificationMessage })) : searchable && searchTerm ? (
|
|
355
|
+
// When searching, show only filtered options
|
|
356
|
+
filteredOptions.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { children: emptyListNode })) : (filteredOptions.map(({ value, label, enabled, keywords }, index) => ((0, jsx_runtime_1.jsx)(SelectOptionItem, { readOnly: readOnly, value: value, label: label, enabled: enabled, keywords: keywords, isHighlighted: selectedIndex === index, itemIndex: index }, value))))) : (
|
|
357
|
+
// When not searching, show all children (includes Options and other components like Button)
|
|
358
|
+
(0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [children, options.size === 0 && (0, jsx_runtime_1.jsx)("div", { children: emptyListNode })] })) })] }) }) }))] }) }), !open && ((0, jsx_runtime_1.jsx)("div", { style: { display: "none" }, children: (0, jsx_runtime_1.jsx)(OptionTypeProvider_1.default, { Component: HiddenOption_1.HiddenOption, children: children }) }))] }) }));
|
|
300
359
|
});
|
|
360
|
+
// Visible option component for rendering items in the dropdown (used by OptionTypeProvider)
|
|
361
|
+
function VisibleSelectOption(option) {
|
|
362
|
+
const { value, label, enabled = true, children } = option;
|
|
363
|
+
const { onOptionAdd } = (0, OptionContext_1.useOption)();
|
|
364
|
+
const { value: selectedValue, onChange, multiSelect, readOnly, setOpen, highlightedValue, optionRenderer, } = (0, SelectContext_1.useSelect)();
|
|
365
|
+
const optionRef = (0, react_1.useRef)(null);
|
|
366
|
+
const opt = (0, react_1.useMemo)(() => {
|
|
367
|
+
return Object.assign(Object.assign({}, option), { label: label !== null && label !== void 0 ? label : "", keywords: option.keywords || [label !== null && label !== void 0 ? label : ""] });
|
|
368
|
+
}, [option, label]);
|
|
369
|
+
(0, react_1.useEffect)(() => {
|
|
370
|
+
onOptionAdd(opt);
|
|
371
|
+
// Don't remove options when component unmounts - they should persist
|
|
372
|
+
}, [opt, onOptionAdd]);
|
|
373
|
+
const selected = (0, react_1.useMemo)(() => {
|
|
374
|
+
return Array.isArray(selectedValue) && multiSelect
|
|
375
|
+
? selectedValue.map((v) => String(v)).includes(value)
|
|
376
|
+
: String(selectedValue) === String(value);
|
|
377
|
+
}, [selectedValue, value, multiSelect]);
|
|
378
|
+
const isHighlighted = (0, react_1.useMemo)(() => {
|
|
379
|
+
return highlightedValue !== undefined && String(highlightedValue) === String(value);
|
|
380
|
+
}, [highlightedValue, value]);
|
|
381
|
+
// Scroll into view when highlighted
|
|
382
|
+
(0, react_1.useEffect)(() => {
|
|
383
|
+
if (isHighlighted && optionRef.current) {
|
|
384
|
+
optionRef.current.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
385
|
+
}
|
|
386
|
+
}, [isHighlighted]);
|
|
387
|
+
const handleClick = () => {
|
|
388
|
+
if (readOnly) {
|
|
389
|
+
setOpen(false);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (enabled) {
|
|
393
|
+
onChange(value);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
return ((0, jsx_runtime_1.jsx)("div", { ref: optionRef, role: "option", "aria-disabled": !enabled, "aria-selected": selected, className: (0, classnames_1.default)(Select_module_scss_1.default.multiSelectOption, {
|
|
397
|
+
[Select_module_scss_1.default.disabledOption]: !enabled,
|
|
398
|
+
[Select_module_scss_1.default.highlighted]: isHighlighted,
|
|
399
|
+
}), onMouseDown: (e) => {
|
|
400
|
+
e.preventDefault();
|
|
401
|
+
e.stopPropagation();
|
|
402
|
+
}, onClick: handleClick, "data-state": selected ? "checked" : undefined, children: (0, jsx_runtime_1.jsx)("div", { className: Select_module_scss_1.default.multiSelectOptionContent, children: optionRenderer ? (optionRenderer({ label, value, enabled }, selectedValue, false)) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [children || label, selected && (0, jsx_runtime_1.jsx)(IconNative_1.default, { name: "checkmark" })] })) }) }));
|
|
403
|
+
}
|
|
404
|
+
// Internal option component for rendering items in the dropdown
|
|
405
|
+
function SelectOptionItem(option) {
|
|
406
|
+
const { value, label, enabled = true, readOnly, children, isHighlighted = false, itemIndex, } = option;
|
|
407
|
+
const { value: selectedValue, onChange, multiSelect, setOpen, setSelectedIndex, optionRenderer, } = (0, SelectContext_1.useSelect)();
|
|
408
|
+
const optionRef = (0, react_1.useRef)(null);
|
|
409
|
+
const selected = (0, react_1.useMemo)(() => {
|
|
410
|
+
return Array.isArray(selectedValue) && multiSelect
|
|
411
|
+
? selectedValue.map((v) => String(v)).includes(value)
|
|
412
|
+
: String(selectedValue) === String(value);
|
|
413
|
+
}, [selectedValue, value, multiSelect]);
|
|
414
|
+
// Scroll into view when highlighted
|
|
415
|
+
(0, react_1.useEffect)(() => {
|
|
416
|
+
if (isHighlighted && optionRef.current) {
|
|
417
|
+
optionRef.current.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
418
|
+
}
|
|
419
|
+
}, [isHighlighted]);
|
|
420
|
+
const handleClick = () => {
|
|
421
|
+
if (readOnly) {
|
|
422
|
+
setOpen(false);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (enabled) {
|
|
426
|
+
onChange(value);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
return ((0, jsx_runtime_1.jsx)("div", { ref: optionRef, role: "option", "aria-disabled": !enabled, "aria-selected": selected, className: (0, classnames_1.default)(Select_module_scss_1.default.multiSelectOption, {
|
|
430
|
+
[Select_module_scss_1.default.disabledOption]: !enabled,
|
|
431
|
+
[Select_module_scss_1.default.highlighted]: isHighlighted,
|
|
432
|
+
}), onMouseDown: (e) => {
|
|
433
|
+
e.preventDefault();
|
|
434
|
+
e.stopPropagation();
|
|
435
|
+
}, onMouseEnter: () => {
|
|
436
|
+
if (itemIndex !== undefined && setSelectedIndex && enabled) {
|
|
437
|
+
setSelectedIndex(itemIndex);
|
|
438
|
+
}
|
|
439
|
+
}, onClick: handleClick, "data-state": selected ? "checked" : undefined, children: (0, jsx_runtime_1.jsx)("div", { className: Select_module_scss_1.default.multiSelectOptionContent, children: optionRenderer ? (optionRenderer({ label, value, enabled }, selectedValue, false)) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [children || label, selected && (0, jsx_runtime_1.jsx)(IconNative_1.default, { name: "checkmark" })] })) }) }));
|
|
440
|
+
}
|
|
@@ -179,10 +179,21 @@ const ComponentAdapter = (0, react_1.forwardRef)(function ComponentAdapter(_a, r
|
|
|
179
179
|
const stableLayoutCss = (0, hooks_1.useShallowCompareMemoize)(cssProps);
|
|
180
180
|
const className = (0, StyleContext_1.useComponentStyle)(stableLayoutCss);
|
|
181
181
|
const { inspectId, refreshInspection } = (0, InspectorContext_1.useInspector)(safeNode, uid);
|
|
182
|
+
// --- Extract context variables (keys starting with "$") from state
|
|
183
|
+
const contextVars = (0, react_1.useMemo)(() => {
|
|
184
|
+
const vars = {};
|
|
185
|
+
for (const key of Object.keys(state)) {
|
|
186
|
+
if (key.startsWith("$")) {
|
|
187
|
+
vars[key] = state[key];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return vars;
|
|
191
|
+
}, [state]);
|
|
182
192
|
// --- Assemble the renderer context we pass down the rendering chain
|
|
183
193
|
const rendererContext = {
|
|
184
194
|
node: safeNode,
|
|
185
195
|
state: state[uid] || constants_1.EMPTY_OBJECT,
|
|
196
|
+
contextVars,
|
|
186
197
|
updateState: memoedUpdateState,
|
|
187
198
|
appContext,
|
|
188
199
|
extractValue: valueExtractor,
|
|
@@ -124,7 +124,9 @@ exports.Container = (0, react_1.memo)((0, react_1.forwardRef)(function Container
|
|
|
124
124
|
const runCodeAsync = (0, misc_1.useEvent)((source, componentUid, options, ...eventArgs) => __awaiter(this, void 0, void 0, function* () {
|
|
125
125
|
var _a, _b, _c;
|
|
126
126
|
// --- Check if the event handler can sign its lifecycle state
|
|
127
|
-
const canSignEventLifecycle = () =>
|
|
127
|
+
const canSignEventLifecycle = () => {
|
|
128
|
+
return componentUid.description !== undefined && (options === null || options === void 0 ? void 0 : options.eventName) !== undefined;
|
|
129
|
+
};
|
|
128
130
|
let changes = [];
|
|
129
131
|
const getComponentStateClone = () => {
|
|
130
132
|
changes.length = 0;
|
|
@@ -441,9 +443,6 @@ exports.Container = (0, react_1.memo)((0, react_1.forwardRef)(function Container
|
|
|
441
443
|
registerComponentApi,
|
|
442
444
|
]);
|
|
443
445
|
const cleanup = (0, misc_1.useEvent)((uid) => {
|
|
444
|
-
// console.log("CLEANUP CALLED FOR", node);
|
|
445
|
-
//TODO cleanup registered component api for that uid
|
|
446
|
-
//TODO cleanup state for that uid
|
|
447
446
|
delete fnsRef.current[uid];
|
|
448
447
|
});
|
|
449
448
|
// --- The container wraps the `renderChild` function to provide that to the child components
|
|
@@ -45,7 +45,9 @@ exports.StateContainer = (0, react_1.memo)((0, react_1.forwardRef)(function Stat
|
|
|
45
45
|
const [version, setVersion] = (0, react_1.useState)(0);
|
|
46
46
|
const routingParams = useRoutingParams();
|
|
47
47
|
const memoedVars = (0, react_1.useRef)(new Map());
|
|
48
|
-
const stateFromOutside = (0, hooks_1.useShallowCompareMemoize)((0, react_1.useMemo)(() =>
|
|
48
|
+
const stateFromOutside = (0, hooks_1.useShallowCompareMemoize)((0, react_1.useMemo)(() => {
|
|
49
|
+
return extractScopedState(parentState, node.uses);
|
|
50
|
+
}, [node.uses, parentState]));
|
|
49
51
|
// --- All state manipulation happens through the container reducer, which is created here.
|
|
50
52
|
// --- This reducer allow collecting state changes for debugging purposes. The `debugView`
|
|
51
53
|
// --- contains the debug configuration; it may enable (or disable) logging.
|
|
@@ -56,10 +58,18 @@ exports.StateContainer = (0, react_1.memo)((0, react_1.forwardRef)(function Stat
|
|
|
56
58
|
const [componentApis, setComponentApis] = (0, react_1.useState)(constants_1.EMPTY_OBJECT);
|
|
57
59
|
const componentStateWithApis = (0, hooks_1.useShallowCompareMemoize)((0, react_1.useMemo)(() => {
|
|
58
60
|
const ret = Object.assign({}, componentState);
|
|
61
|
+
// Get set of registered API keys - only these should have reducer state exposed as string keys
|
|
62
|
+
const registeredApiKeys = new Set(Object.getOwnPropertySymbols(componentApis)
|
|
63
|
+
.map(s => s.description)
|
|
64
|
+
.filter((d) => d !== undefined));
|
|
59
65
|
for (const stateKey of Object.getOwnPropertySymbols(componentState)) {
|
|
60
66
|
const value = componentState[stateKey];
|
|
61
67
|
if (stateKey.description) {
|
|
62
|
-
|
|
68
|
+
// Only copy reducer state to string keys for APIs registered in THIS container
|
|
69
|
+
// This prevents child containers from inheriting reducer state for parent APIs
|
|
70
|
+
if (registeredApiKeys.has(stateKey.description)) {
|
|
71
|
+
ret[stateKey.description] = value;
|
|
72
|
+
}
|
|
63
73
|
}
|
|
64
74
|
}
|
|
65
75
|
if (Reflect.ownKeys(componentApis).length === 0) {
|
|
@@ -104,16 +114,17 @@ exports.StateContainer = (0, react_1.memo)((0, react_1.forwardRef)(function Stat
|
|
|
104
114
|
const localVarsStateContextWithPreResolvedLocalVars = (0, hooks_1.useShallowCompareMemoize)(Object.assign(Object.assign({}, preResolvedLocalVars), localVarsStateContext));
|
|
105
115
|
const resolvedLocalVars = useVars(varDefinitions, functionDeps, localVarsStateContextWithPreResolvedLocalVars, memoedVars);
|
|
106
116
|
const mergedWithVars = useMergedState(resolvedLocalVars, componentStateWithApis);
|
|
107
|
-
const combinedState = useCombinedState(stateFromOutside,
|
|
117
|
+
const combinedState = useCombinedState(stateFromOutside, // Parent state (lower priority) - allows local vars to shadow
|
|
118
|
+
node.contextVars, // Context vars like $item
|
|
119
|
+
mergedWithVars, // Local vars and component state (higher priority) - enables shadowing
|
|
120
|
+
routingParams);
|
|
108
121
|
const registerComponentApi = (0, react_1.useCallback)((uid, api) => {
|
|
109
122
|
setComponentApis((0, immer_1.default)((draft) => {
|
|
110
|
-
// console.log("-----BUST----setComponentApis");
|
|
111
123
|
if (!draft[uid]) {
|
|
112
124
|
draft[uid] = {};
|
|
113
125
|
}
|
|
114
126
|
Object.entries(api).forEach(([key, value]) => {
|
|
115
127
|
if (draft[uid][key] !== value) {
|
|
116
|
-
// console.log(`-----BUST------new api for ${uid}`, draft[uid][key], value)
|
|
117
128
|
draft[uid][key] = value;
|
|
118
129
|
}
|
|
119
130
|
});
|
|
@@ -185,11 +196,9 @@ function useCombinedState(...states) {
|
|
|
185
196
|
const combined = (0, react_1.useMemo)(() => {
|
|
186
197
|
let ret = {};
|
|
187
198
|
states.forEach((state = constants_1.EMPTY_OBJECT) => {
|
|
188
|
-
// console.log("st", state);
|
|
189
199
|
if (state !== constants_1.EMPTY_OBJECT) {
|
|
190
200
|
ret = Object.assign(Object.assign({}, ret), state);
|
|
191
201
|
}
|
|
192
|
-
// console.log("ret", ret);
|
|
193
202
|
});
|
|
194
203
|
return ret;
|
|
195
204
|
}, [states]);
|
|
@@ -244,7 +253,6 @@ function useVars(vars = constants_1.EMPTY_OBJECT, fnDeps = constants_1.EMPTY_OBJ
|
|
|
244
253
|
if (isParsedValue(value)) {
|
|
245
254
|
return (0, visitors_1.collectVariableDependencies)(value.tree, referenceTrackedApi);
|
|
246
255
|
}
|
|
247
|
-
// console.log(`GETTING DEPENDENCY FOR ${value} with:`, referenceTrackedApi);
|
|
248
256
|
const params = (0, ParameterParser_1.parseParameterString)(value);
|
|
249
257
|
let ret = new Set();
|
|
250
258
|
params.forEach((param) => {
|
|
@@ -258,15 +266,6 @@ function useVars(vars = constants_1.EMPTY_OBJECT, fnDeps = constants_1.EMPTY_OBJ
|
|
|
258
266
|
return Array.from(ret);
|
|
259
267
|
}),
|
|
260
268
|
obtainValue: (0, memoize_one_1.default)((value, state, appContext, strict, deps, appContextDeps) => {
|
|
261
|
-
// console.log(
|
|
262
|
-
// "VARS, BUST, obtain value called with",
|
|
263
|
-
// value,
|
|
264
|
-
// { state, appContext },
|
|
265
|
-
// {
|
|
266
|
-
// deps,
|
|
267
|
-
// appContextDeps,
|
|
268
|
-
// }
|
|
269
|
-
// );
|
|
270
269
|
try {
|
|
271
270
|
return isParsedValue(value)
|
|
272
271
|
? (0, eval_tree_sync_1.evalBinding)(value.tree, {
|
|
@@ -309,7 +308,6 @@ function useVars(vars = constants_1.EMPTY_OBJECT, fnDeps = constants_1.EMPTY_OBJ
|
|
|
309
308
|
}
|
|
310
309
|
const stateDepValues = (0, misc_1.pickFromObject)(stateContext, dependencies);
|
|
311
310
|
const appContextDepValues = (0, misc_1.pickFromObject)(appContext, dependencies);
|
|
312
|
-
// console.log("VARS, obtain value called with", stateDepValues, appContextDepValues);
|
|
313
311
|
ret[key] = memoedVars.current
|
|
314
312
|
.get(value)
|
|
315
313
|
.obtainValue(value, stateContext, appContext, true, stateDepValues, appContextDepValues);
|
|
@@ -70,21 +70,24 @@ function createContainerReducer(debugView) {
|
|
|
70
70
|
case containers_1.ContainerActionKind.EVENT_HANDLER_STARTED: {
|
|
71
71
|
const { eventName } = action.payload;
|
|
72
72
|
const inProgressFlagName = `${eventName}InProgress`;
|
|
73
|
-
|
|
73
|
+
// Preserve existing state or use empty object
|
|
74
|
+
state[uid] = state[uid] ? Object.assign(Object.assign({}, state[uid]), { [inProgressFlagName]: true }) : { [inProgressFlagName]: true };
|
|
74
75
|
storeNextValue(state[uid]);
|
|
75
76
|
break;
|
|
76
77
|
}
|
|
77
78
|
case containers_1.ContainerActionKind.EVENT_HANDLER_COMPLETED: {
|
|
78
79
|
const { eventName } = action.payload;
|
|
79
80
|
const inProgressFlagName = `${eventName}InProgress`;
|
|
80
|
-
|
|
81
|
+
// Preserve existing state or use empty object
|
|
82
|
+
state[uid] = state[uid] ? Object.assign(Object.assign({}, state[uid]), { [inProgressFlagName]: false }) : { [inProgressFlagName]: false };
|
|
81
83
|
storeNextValue(state[uid]);
|
|
82
84
|
break;
|
|
83
85
|
}
|
|
84
86
|
case containers_1.ContainerActionKind.EVENT_HANDLER_ERROR: {
|
|
85
87
|
const { eventName } = action.payload;
|
|
86
88
|
const inProgressFlagName = `${eventName}InProgress`;
|
|
87
|
-
|
|
89
|
+
// Preserve existing state or use empty object
|
|
90
|
+
state[uid] = state[uid] ? Object.assign(Object.assign({}, state[uid]), { [inProgressFlagName]: false }) : { [inProgressFlagName]: false };
|
|
88
91
|
storeNextValue(state[uid]);
|
|
89
92
|
break;
|
|
90
93
|
}
|
|
@@ -693,7 +693,7 @@ class SelectDriver extends ComponentDriver {
|
|
|
693
693
|
}
|
|
694
694
|
searchFor(value) {
|
|
695
695
|
return __awaiter(this, void 0, void 0, function* () {
|
|
696
|
-
yield this.page.getByRole("
|
|
696
|
+
yield this.page.getByRole("searchbox").fill(value);
|
|
697
697
|
});
|
|
698
698
|
}
|
|
699
699
|
chooseIndex(index) {
|
|
@@ -1322,6 +1322,7 @@ declare type RenderChildFn<L extends ComponentDef = ComponentDef> = (children?:
|
|
|
1322
1322
|
declare interface RendererContext<TMd extends ComponentMetadata = ComponentMetadata> extends ComponentRendererContextBase<TMd> {
|
|
1323
1323
|
uid: symbol;
|
|
1324
1324
|
updateState: UpdateStateFn;
|
|
1325
|
+
contextVars: Record<string, any>;
|
|
1325
1326
|
extractValue: ValueExtractor;
|
|
1326
1327
|
extractResourceUrl: (url?: string) => string | undefined;
|
|
1327
1328
|
lookupEventHandler: LookupEventHandlerFn<TMd>;
|