xmlui 0.10.23 → 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.
Files changed (42) hide show
  1. package/dist/lib/{index-CuPvcayg.mjs → index-CCEPGw_x.mjs} +704 -506
  2. package/dist/lib/index.css +1 -1
  3. package/dist/lib/{initMock-BBdNO6FB.mjs → initMock-DFcCR7ey.mjs} +1 -1
  4. package/dist/lib/xmlui.d.ts +1 -0
  5. package/dist/lib/xmlui.mjs +1 -1
  6. package/dist/metadata/{collectedComponentMetadata-Cp3Ljk8F.mjs → collectedComponentMetadata-mwkNkxN_.mjs} +700 -502
  7. package/dist/metadata/{initMock-Dki8247s.mjs → initMock-BVxHA6wu.mjs} +1 -1
  8. package/dist/metadata/style.css +1 -1
  9. package/dist/metadata/xmlui-metadata.mjs +1 -1
  10. package/dist/metadata/xmlui-metadata.umd.js +3 -3
  11. package/dist/scripts/package.json +1 -1
  12. package/dist/scripts/src/components/AppHeader/AppHeaderNative.js +2 -1
  13. package/dist/scripts/src/components/AutoComplete/AutoComplete.spec.js +1 -1
  14. package/dist/scripts/src/components/AutoComplete/AutoCompleteNative.js +30 -5
  15. package/dist/scripts/src/components/CodeBlock/highlight-code.js +5 -32
  16. package/dist/scripts/src/components/Form/Form.spec.js +2 -2
  17. package/dist/scripts/src/components/FormItem/FormItemNative.js +5 -1
  18. package/dist/scripts/src/components/Markdown/MarkdownNative.js +4 -3
  19. package/dist/scripts/src/components/Markdown/utils.js +4 -3
  20. package/dist/scripts/src/components/ModalDialog/ModalDialog.js +4 -6
  21. package/dist/scripts/src/components/ModalDialog/ModalDialog.spec.js +19 -0
  22. package/dist/scripts/src/components/NavGroup/NavGroup.spec.js +103 -11
  23. package/dist/scripts/src/components/NavGroup/NavGroupNative.js +6 -1
  24. package/dist/scripts/src/components/NestedApp/utils.js +6 -5
  25. package/dist/scripts/src/components/Option/Option.spec.js +3 -1
  26. package/dist/scripts/src/components/Select/HiddenOption.js +3 -3
  27. package/dist/scripts/src/components/Select/Select.js +2 -3
  28. package/dist/scripts/src/components/Select/Select.spec.js +53 -6
  29. package/dist/scripts/src/components/Select/SelectNative.js +187 -47
  30. package/dist/scripts/src/components-core/rendering/ComponentAdapter.js +11 -0
  31. package/dist/scripts/src/components-core/rendering/Container.js +3 -4
  32. package/dist/scripts/src/components-core/rendering/StateContainer.js +16 -18
  33. package/dist/scripts/src/components-core/rendering/reducer.js +6 -3
  34. package/dist/scripts/src/components-core/utils/base64-utils.js +122 -0
  35. package/dist/scripts/src/components-core/utils/date-utils.js +12 -3
  36. package/dist/scripts/src/testing/ComponentDrivers.js +1 -1
  37. package/dist/standalone/xmlui-standalone.es.d.ts +1 -0
  38. package/dist/standalone/xmlui-standalone.umd.js +15 -15
  39. package/package.json +1 -1
  40. package/dist/scripts/src/components/Select/MultiSelectOption.js +0 -42
  41. package/dist/scripts/src/components/Select/SelectOption.js +0 -34
  42. package/dist/scripts/src/components/Select/SimpleSelect.js +0 -57
@@ -38,10 +38,10 @@ fixtures_1.test.describe("Basic Functionality", () => {
38
38
  </FormItem>
39
39
  </Form>`);
40
40
  const driver = yield createSelectDriver("mySelect");
41
- yield (0, fixtures_1.expect)(driver.component.locator("select")).toHaveValue("opt1");
41
+ yield (0, fixtures_1.expect)(driver.component).toHaveText("first");
42
42
  yield driver.toggleOptionsVisibility();
43
43
  yield driver.selectLabel("second");
44
- yield (0, fixtures_1.expect)(driver.component.locator("select")).toHaveValue("opt2");
44
+ yield (0, fixtures_1.expect)(driver.component).toHaveText("second");
45
45
  }));
46
46
  // --- initialValue prop
47
47
  (0, fixtures_1.test)("initialValue set to first valid value", (_a) => __awaiter(void 0, [_a], void 0, function* ({ page, initTestBed }) {
@@ -133,12 +133,10 @@ fixtures_1.test.describe("Basic Functionality", () => {
133
133
  </Select>
134
134
  `);
135
135
  const driver = yield createSelectDriver();
136
- yield (0, fixtures_1.expect)(page.getByText("Two")).not.toBeVisible();
137
- yield (0, fixtures_1.expect)(page.getByText("One")).toBeVisible();
136
+ yield (0, fixtures_1.expect)(driver.component).toHaveText("One");
138
137
  yield driver.toggleOptionsVisibility();
139
138
  yield driver.selectLabel("Two");
140
- yield (0, fixtures_1.expect)(page.getByText("Two")).not.toBeVisible();
141
- yield (0, fixtures_1.expect)(page.getByText("One")).toBeVisible();
139
+ yield (0, fixtures_1.expect)(driver.component).toHaveText("One");
142
140
  // verify dropdown is not visible but value is shown
143
141
  }));
144
142
  (0, fixtures_1.test)("readOnly multi-Select shows options, but value cannot be changed", (_a) => __awaiter(void 0, [_a], void 0, function* ({ page, initTestBed, createSelectDriver, }) {
@@ -622,3 +620,52 @@ fixtures_1.test.describe("Visual State", () => {
622
620
  (0, fixtures_1.expect)(width).toBe(200);
623
621
  }));
624
622
  });
623
+ // =============================================================================
624
+ // Z-INDEX AND MODAL LAYERING TESTS
625
+ // =============================================================================
626
+ fixtures_1.test.describe("Z-Index and Modal Layering", () => {
627
+ (0, fixtures_1.test)("Select dropdown in modal is visible and not covered by modal overlay", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page, createSelectDriver, }) {
628
+ yield initTestBed(`
629
+ <Fragment>
630
+ <Select testId="select">
631
+ <Option value="stuff1">option 1</Option>
632
+ <Option value="stuff2">option 2</Option>
633
+ <Button onClick="modal.open()">BLOW UP</Button>
634
+ </Select>
635
+ <ModalDialog id="modal" title="Example Dialog">
636
+ <Form data="{{ firstName: 'Billy', lastName: 'Bob' }}">
637
+ <FormItem bindTo="firstName" required="true" />
638
+ <FormItem bindTo="lastName" required="true" />
639
+ <FormItem
640
+ label="Field to Update"
641
+ type="select"
642
+ width="200px"
643
+ bindTo="fieldToUpdate"
644
+ required
645
+ initialValue="rate"
646
+ testId="modal-select"
647
+ >
648
+ <Option value="rate">Price</Option>
649
+ <Option value="description">Item Description</Option>
650
+ <Option value="account_id">Account</Option>
651
+ </FormItem>
652
+ </Form>
653
+ </ModalDialog>
654
+ </Fragment>
655
+ `);
656
+ const selectDriver = yield createSelectDriver("select");
657
+ yield selectDriver.click();
658
+ // Click button to open modal
659
+ const blowUpButton = page.getByText("BLOW UP");
660
+ yield blowUpButton.click();
661
+ // Wait for modal to be visible
662
+ yield (0, fixtures_1.expect)(page.getByRole("dialog", { name: "Example Dialog" })).toBeVisible();
663
+ // Open the select in the modal
664
+ const modalSelectDriver = yield createSelectDriver("modal-select");
665
+ yield modalSelectDriver.click();
666
+ // Check that all options are visible
667
+ yield (0, fixtures_1.expect)(page.getByRole("option", { name: "Price" })).toBeVisible();
668
+ yield (0, fixtures_1.expect)(page.getByRole("option", { name: "Item Description" })).toBeVisible();
669
+ yield (0, fixtures_1.expect)(page.getByRole("option", { name: "Account" })).toBeVisible();
670
+ }));
671
+ });
@@ -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 react_2 = require("react");
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 FormContext_1 = require("../Form/FormContext");
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, react_2.forwardRef)(function Select(_a, forwardedRef) {
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, react_2.useState)(null);
100
- const [open, setOpen] = (0, react_2.useState)(false);
101
- const [width, setWidth] = (0, react_2.useState)(0);
102
- const observer = (0, react_2.useRef)();
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, react_2.useState)(new Set());
105
- const isInForm = (0, FormContext_1.useIsInsideForm)();
106
- const [searchTerm, setSearchTerm] = (0, react_2.useState)("");
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, react_2.useMemo)(() => {
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, react_2.useEffect)(() => {
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, react_2.useEffect)(() => {
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, react_2.useEffect)(() => {
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, react_2.useCallback)((selectedValue) => {
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, react_2.useCallback)(() => {
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, react_2.useCallback)((currentIndex) => {
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, react_2.useCallback)((currentIndex) => {
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, react_2.useCallback)((event) => {
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, react_2.useCallback)(() => {
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, react_2.useEffect)(() => {
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, react_2.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]);
268
- const onOptionAdd = (0, react_2.useCallback)((option) => {
269
- setOptions((prev) => new Set(prev).add(option));
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, react_2.useCallback)((option) => {
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, react_2.useMemo)(() => ({
290
+ const optionContextValue = (0, react_1.useMemo)(() => ({
279
291
  onOptionAdd,
280
292
  onOptionRemove,
281
293
  }), [onOptionAdd, onOptionRemove]);
282
- const selectContextValue = (0, react_2.useMemo)(() => ({
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
- }), [multiSelect, toggleOption, value, options, optionRenderer]);
291
- return ((0, jsx_runtime_1.jsx)(SelectContext_1.SelectContext.Provider, { value: selectContextValue, children: (0, jsx_runtime_1.jsx)(OptionContext_1.OptionContext.Provider, { value: optionContextValue, children: searchable || multiSelect ? ((0, jsx_runtime_1.jsxs)(OptionTypeProvider_1.default, { Component: HiddenOption_1.HiddenOption, children: [(0, jsx_runtime_1.jsxs)(react_popover_1.Popover, { open: open, onOpenChange: setOpen, 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, onClick: (event) => {
292
- // Prevent event propagation to parent elements (e.g., DropdownMenu)
293
- // This ensures that clicking the Select trigger doesn't close the containing DropdownMenu
294
- event.stopPropagation();
295
- setOpen((prev) => !prev);
296
- }, className: (0, classnames_1.default)(Select_module_scss_1.default.selectTrigger, Select_module_scss_1.default[validationStatus], {
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
- }, className), 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_focus_scope_1.FocusScope, { asChild: true, loop: true, trapped: true, 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: "combobox", className: (0, classnames_1.default)(Select_module_scss_1.default.commandInput), placeholder: "Search...", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), autoFocus: true })] })) : ((0, jsx_runtime_1.jsx)("button", { autoFocus: true, "aria-hidden": "true", className: Select_module_scss_1.default.srOnly })), (0, jsx_runtime_1.jsxs)("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 })), filteredOptions.map(({ value, label, enabled, keywords }, index) => ((0, jsx_runtime_1.jsx)(MultiSelectOption_1.MultiSelectOption, { readOnly: readOnly, value: value, label: label, enabled: enabled, keywords: keywords, isHighlighted: selectedIndex === index, itemIndex: index }, value))), !inProgress && filteredOptions.length === 0 && ((0, jsx_runtime_1.jsx)("div", { children: emptyListNode }))] })] }) }) }) }))] }), children] })) : ((0, jsx_runtime_1.jsx)(OptionTypeProvider_1.default, { Component: SelectOption_1.SelectOption, children: (0, react_1.createElement)(SimpleSelect_1.SimpleSelect, Object.assign({}, rest, { readOnly: !!readOnly, ref: forwardedRef, key: isInForm ? (value ? `status-${value}` : "status-initial") : undefined, value: value, onValueChange: toggleOption, id: id, style: style, className: className, onFocus: onFocus, onBlur: onBlur, enabled: enabled, validationStatus: validationStatus, triggerRef: setReferenceElement, autoFocus: autoFocus, placeholder: placeholder, height: dropdownHeight, width: width, emptyListNode: emptyListNode }), children) })) }) }));
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 = () => componentUid.description !== undefined && (options === null || options === void 0 ? void 0 : options.eventName) !== undefined;
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)(() => extractScopedState(parentState, node.uses), [node.uses, parentState]));
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
- ret[stateKey.description] = value;
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, node.contextVars, mergedWithVars, routingParams);
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
- state[uid] = Object.assign(Object.assign({}, state[uid]), { [inProgressFlagName]: true });
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
- state[uid] = Object.assign(Object.assign({}, state[uid]), { [inProgressFlagName]: false });
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
- state[uid] = Object.assign(Object.assign({}, state[uid]), { [inProgressFlagName]: false });
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
  }