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.
Files changed (36) hide show
  1. package/dist/lib/{index-DoIVkz5T.mjs → index-CCEPGw_x.mjs} +476 -477
  2. package/dist/lib/index.css +1 -1
  3. package/dist/lib/{initMock-CSGEd746.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-B3Hs8_cV.mjs → collectedComponentMetadata-mwkNkxN_.mjs} +472 -473
  7. package/dist/metadata/{initMock-DQrGwkya.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/Form/Form.spec.js +2 -2
  16. package/dist/scripts/src/components/FormItem/FormItemNative.js +5 -1
  17. package/dist/scripts/src/components/ModalDialog/ModalDialog.js +4 -6
  18. package/dist/scripts/src/components/ModalDialog/ModalDialog.spec.js +19 -0
  19. package/dist/scripts/src/components/NavGroup/NavGroup.spec.js +103 -11
  20. package/dist/scripts/src/components/NavGroup/NavGroupNative.js +6 -1
  21. package/dist/scripts/src/components/Option/Option.spec.js +3 -1
  22. package/dist/scripts/src/components/Select/HiddenOption.js +3 -3
  23. package/dist/scripts/src/components/Select/Select.js +2 -3
  24. package/dist/scripts/src/components/Select/Select.spec.js +4 -6
  25. package/dist/scripts/src/components/Select/SelectNative.js +187 -47
  26. package/dist/scripts/src/components-core/rendering/ComponentAdapter.js +11 -0
  27. package/dist/scripts/src/components-core/rendering/Container.js +3 -4
  28. package/dist/scripts/src/components-core/rendering/StateContainer.js +16 -18
  29. package/dist/scripts/src/components-core/rendering/reducer.js +6 -3
  30. package/dist/scripts/src/testing/ComponentDrivers.js +1 -1
  31. package/dist/standalone/xmlui-standalone.es.d.ts +1 -0
  32. package/dist/standalone/xmlui-standalone.umd.js +18 -18
  33. package/package.json +1 -1
  34. package/dist/scripts/src/components/Select/MultiSelectOption.js +0 -42
  35. package/dist/scripts/src/components/Select/SelectOption.js +0 -34
  36. package/dist/scripts/src/components/Select/SimpleSelect.js +0 -57
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xmlui",
3
- "version": "0.10.24",
3
+ "version": "0.10.25",
4
4
  "sideEffects": false,
5
5
  "scripts": {
6
6
  "start-test-bed": "cd src/testing/infrastructure && xmlui start",
@@ -30,6 +30,7 @@ const AppLayoutContext_1 = require("../../components/App/AppLayoutContext");
30
30
  const ButtonNative_1 = require("../../components/Button/ButtonNative");
31
31
  const NavLinkNative_1 = require("../../components/NavLink/NavLinkNative");
32
32
  const hooks_1 = require("../../components-core/utils/hooks");
33
+ const PART_HAMBURGER = "hamburger";
33
34
  exports.defaultProps = {
34
35
  showLogo: true,
35
36
  showNavPanelIf: true,
@@ -57,7 +58,7 @@ const AppHeader = (_a) => {
57
58
  }, []);
58
59
  return ((0, jsx_runtime_1.jsx)("div", Object.assign({}, rest, { className: (0, classnames_1.default)(AppHeader_module_scss_1.default.header, className), style: style, role: "banner", children: (0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)(AppHeader_module_scss_1.default.headerInner, {
59
60
  [AppHeader_module_scss_1.default.full]: !canRestrictContentWidth,
60
- }), children: [hasRegisteredNavPanel && showNavPanelIf && ((0, jsx_runtime_1.jsx)(ButtonNative_1.Button, { onClick: toggleDrawer, icon: (0, jsx_runtime_1.jsx)(IconNative_1.Icon, { name: "hamburger" }), variant: "ghost", className: AppHeader_module_scss_1.default.drawerToggle, style: { color: "inherit", flexShrink: 0 } })), (0, jsx_runtime_1.jsx)("div", { className: AppHeader_module_scss_1.default.logoAndTitle, children: (showLogo || !effectiveNavPanelVisible) &&
61
+ }), children: [hasRegisteredNavPanel && showNavPanelIf && ((0, jsx_runtime_1.jsx)(ButtonNative_1.Button, { "data-part-id": PART_HAMBURGER, onClick: toggleDrawer, icon: (0, jsx_runtime_1.jsx)(IconNative_1.Icon, { name: "hamburger" }), variant: "ghost", className: AppHeader_module_scss_1.default.drawerToggle, style: { color: "inherit", flexShrink: 0 } })), (0, jsx_runtime_1.jsx)("div", { className: AppHeader_module_scss_1.default.logoAndTitle, children: (showLogo || !effectiveNavPanelVisible) &&
61
62
  (logoContent ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: AppHeader_module_scss_1.default.customLogoContainer, children: logoContent }), safeLogoTitle] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [!!logoUrl && ((0, jsx_runtime_1.jsx)("div", { className: AppHeader_module_scss_1.default.logoContainer, children: (0, jsx_runtime_1.jsx)(NavLinkNative_1.NavLink, { to: "/", displayActive: false, className: AppHeader_module_scss_1.default.logoLink, children: (0, jsx_runtime_1.jsx)(LogoNative_1.Logo, {}) }) })), safeLogoTitle] }))) }), (0, jsx_runtime_1.jsx)("div", { ref: subNavPanelSlot, className: AppHeader_module_scss_1.default.subNavPanelSlot }), (0, jsx_runtime_1.jsx)("div", { className: AppHeader_module_scss_1.default.childrenWrapper, children: children }), profileMenu && (0, jsx_runtime_1.jsx)("div", { className: AppHeader_module_scss_1.default.rightItems, children: profileMenu })] }) })));
62
63
  };
63
64
  exports.AppHeader = AppHeader;
@@ -115,10 +115,10 @@ const fixtures_1 = require("../../testing/fixtures");
115
115
  // First option should be selected
116
116
  yield (0, fixtures_1.expect)(page.getByTestId("text")).toHaveText("Selected values: 1");
117
117
  // Click another option
118
- yield driver.click();
119
118
  yield driver.selectLabel("Diana Prince");
120
119
  // Both options should be selected
121
120
  yield (0, fixtures_1.expect)(page.getByTestId("text")).toHaveText("Selected values: 1,3");
121
+ yield driver.click();
122
122
  // Both selected options should be visible as badges
123
123
  yield (0, fixtures_1.expect)(page.getByText("Bruce Wayne")).toBeVisible();
124
124
  yield (0, fixtures_1.expect)(page.getByText("Diana Prince")).toBeVisible();
@@ -162,7 +162,16 @@ exports.AutoComplete = (0, react_1.forwardRef)(function AutoComplete(_a, forward
162
162
  onDidChange(newValue);
163
163
  }, [multi, updateState, onDidChange]);
164
164
  const onOptionAdd = (0, react_1.useCallback)((option) => {
165
- setOptions((prev) => new Set(prev).add(option));
165
+ setOptions((prev) => {
166
+ const newSet = new Set(prev);
167
+ // Remove old version if exists, then add the new one to ensure updates
168
+ const existing = Array.from(prev).find((opt) => opt.value === option.value);
169
+ if (existing) {
170
+ newSet.delete(existing);
171
+ }
172
+ newSet.add(option);
173
+ return newSet;
174
+ });
166
175
  }, []);
167
176
  const onOptionRemove = (0, react_1.useCallback)((option) => {
168
177
  setOptions((prev) => {
@@ -363,7 +372,17 @@ exports.AutoComplete = (0, react_1.forwardRef)(function AutoComplete(_a, forward
363
372
  }, modal: false, children: [(0, jsx_runtime_1.jsx)(react_popover_1.PopoverTrigger, { asChild: true, ref: setReferenceElement, children: (0, jsx_runtime_1.jsxs)("div", { ref: forwardedRef, style: style, "data-part-id": PART_LIST_WRAPPER, className: (0, classnames_1.default)(className, AutoComplete_module_scss_1.default.badgeListWrapper, AutoComplete_module_scss_1.default[validationStatus], {
364
373
  [AutoComplete_module_scss_1.default.disabled]: !enabled,
365
374
  [AutoComplete_module_scss_1.default.focused]: isFocused,
366
- }), "aria-expanded": open, children: [Array.isArray(selectedValue) && selectedValue.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: AutoComplete_module_scss_1.default.badgeList, children: selectedValue.map((v, index) => ((0, jsx_runtime_1.jsxs)("span", { className: AutoComplete_module_scss_1.default.badge, children: [v === null || v === void 0 ? void 0 : v.label, !readOnly && ((0, jsx_runtime_1.jsx)(IconNative_1.default, { name: "close", size: "sm", onClick: (event) => {
375
+ }), "aria-expanded": open, onClick: (event) => {
376
+ if (readOnly)
377
+ return;
378
+ // In multi mode, only open the dropdown, don't toggle
379
+ // In single mode, toggle as usual
380
+ if (multi && open) {
381
+ return; // Already open, don't close
382
+ }
383
+ event.stopPropagation();
384
+ setOpen((prev) => !prev);
385
+ }, children: [Array.isArray(selectedValue) && selectedValue.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: AutoComplete_module_scss_1.default.badgeList, children: selectedValue.map((v, index) => ((0, jsx_runtime_1.jsxs)("span", { className: AutoComplete_module_scss_1.default.badge, children: [v === null || v === void 0 ? void 0 : v.label, !readOnly && ((0, jsx_runtime_1.jsx)(IconNative_1.default, { name: "close", size: "sm", onClick: (event) => {
367
386
  event.stopPropagation();
368
387
  toggleOption(v.value);
369
388
  } }))] }, index))) })), (0, jsx_runtime_1.jsxs)("div", { className: AutoComplete_module_scss_1.default.inputWrapper, children: [(0, jsx_runtime_1.jsx)("input", Object.assign({}, rest, { role: "combobox", id: id, ref: inputRef, onFocus: (ev) => {
@@ -418,7 +437,7 @@ exports.AutoComplete = (0, react_1.forwardRef)(function AutoComplete(_a, forward
418
437
  }) })] }) }) }))] }), children] }) }) }));
419
438
  });
420
439
  function CreatableItem({ onNewItem, isHighlighted = false }) {
421
- const { value, options, searchTerm, onChange, setOpen, setSelectedIndex } = (0, AutoCompleteContext_1.useAutoComplete)();
440
+ const { value, options, searchTerm, onChange, setOpen, setSelectedIndex, multi } = (0, AutoCompleteContext_1.useAutoComplete)();
422
441
  const { onOptionAdd } = (0, OptionContext_1.useOption)();
423
442
  if (isOptionsExist(options, [{ value: searchTerm, label: searchTerm }]) ||
424
443
  (Array.isArray(value) && (value === null || value === void 0 ? void 0 : value.find((s) => s === searchTerm))) ||
@@ -430,7 +449,10 @@ function CreatableItem({ onNewItem, isHighlighted = false }) {
430
449
  onOptionAdd(newOption);
431
450
  onNewItem === null || onNewItem === void 0 ? void 0 : onNewItem(searchTerm);
432
451
  onChange(searchTerm);
433
- setOpen(false);
452
+ // Only close dropdown for single select mode
453
+ if (!multi) {
454
+ setOpen(false);
455
+ }
434
456
  };
435
457
  const Item = ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(AutoComplete_module_scss_1.default.autoCompleteOption, {
436
458
  [AutoComplete_module_scss_1.default.highlighted]: isHighlighted,
@@ -456,7 +478,10 @@ function AutoCompleteOption(option) {
456
478
  const handleClick = () => {
457
479
  if (!readOnly && enabled) {
458
480
  onChange(value);
459
- setOpen(false);
481
+ // Only close dropdown for single select mode
482
+ if (!multi) {
483
+ setOpen(false);
484
+ }
460
485
  }
461
486
  };
462
487
  return ((0, jsx_runtime_1.jsx)("div", { id: id, role: "option", "aria-disabled": !enabled, "aria-selected": selected, className: (0, classnames_1.default)(AutoComplete_module_scss_1.default.autoCompleteOption, {
@@ -674,7 +674,7 @@ fixtures_1.test.describe("Edge Cases", () => {
674
674
  const textfieldElement = (yield createFormItemDriver("name2")).input;
675
675
  const textfieldDriver = yield createTextBoxDriver(textfieldElement);
676
676
  yield selectDriver.component.click();
677
- yield page.getByLabel("Public Key").click();
677
+ yield page.getByText("Public Key").click();
678
678
  yield textfieldDriver.field.fill("John");
679
679
  yield formDriver.submitForm();
680
680
  yield fixtures_1.expect.poll(testStateDriver.testState).toEqual(true);
@@ -1242,7 +1242,7 @@ const smartCrudInterceptor = {
1242
1242
  const textfieldElement = (yield createFormItemDriver("name2")).input;
1243
1243
  const textfieldDriver = yield createTextBoxDriver(textfieldElement);
1244
1244
  yield selectDriver.component.click();
1245
- yield page.getByLabel("Public Key").click();
1245
+ yield page.getByText("Public Key").click();
1246
1246
  yield textfieldDriver.field.fill("John");
1247
1247
  yield formDriver.submitForm();
1248
1248
  yield fixtures_1.expect.poll(testStateDriver.testState).toEqual(true);
@@ -10,6 +10,9 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  }
11
11
  return t;
12
12
  };
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
13
16
  Object.defineProperty(exports, "__esModule", { value: true });
14
17
  exports.FormItem = exports.defaultProps = void 0;
15
18
  exports.CustomFormItem = CustomFormItem;
@@ -38,6 +41,7 @@ const HelperText_1 = require("./HelperText");
38
41
  const ItemsNative_1 = require("../Items/ItemsNative");
39
42
  const constants_1 = require("../../components-core/constants");
40
43
  const hooks_1 = require("../../components-core/utils/hooks");
44
+ const FormItem_module_scss_1 = __importDefault(require("./FormItem.module.scss"));
41
45
  const DEFAULT_LABEL_POSITIONS = {
42
46
  checkbox: "end",
43
47
  };
@@ -221,7 +225,7 @@ exports.FormItem = (0, react_1.memo)(function FormItem(_a) {
221
225
  if (!isInsideForm) {
222
226
  throw new Error("FormItem must be used inside a Form");
223
227
  }
224
- return ((0, jsx_runtime_1.jsx)(ItemWithLabel_1.ItemWithLabel, { labelPosition: labelPositionValue, label: label, labelWidth: labelWidthValue, labelBreak: labelBreakValue, enabled: isEnabled, required: validations.required, validationInProgress: validationResult === null || validationResult === void 0 ? void 0 : validationResult.partial, onFocus: onFocus, onBlur: onBlur, style: style, className: className, validationResult: (0, jsx_runtime_1.jsx)("div", { ref: animateContainerRef, children: isHelperTextShown &&
228
+ return ((0, jsx_runtime_1.jsx)(ItemWithLabel_1.ItemWithLabel, { labelPosition: labelPositionValue, label: label, labelWidth: labelWidthValue, labelBreak: labelBreakValue, enabled: isEnabled, required: validations.required, validationInProgress: validationResult === null || validationResult === void 0 ? void 0 : validationResult.partial, onFocus: onFocus, onBlur: onBlur, style: style, className: className, validationResult: (0, jsx_runtime_1.jsx)("div", { ref: animateContainerRef, className: FormItem_module_scss_1.default.helperTextContainer, children: isHelperTextShown &&
225
229
  (validationResult === null || validationResult === void 0 ? void 0 : validationResult.validations.map((singleValidation, i) => ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [singleValidation.isValid && !!singleValidation.validMessage && ((0, jsx_runtime_1.jsx)(HelperText_1.HelperText, { text: singleValidation.validMessage, status: "valid", style: { opacity: singleValidation.stale ? 0.5 : undefined } })), !singleValidation.isValid && !!singleValidation.invalidMessage && ((0, jsx_runtime_1.jsx)(HelperText_1.HelperText, { text: singleValidation.invalidMessage, status: singleValidation.severity, style: { opacity: singleValidation.stale ? 0.5 : undefined } }))] }, i)))) }), children: formControl }));
226
230
  });
227
231
  function CustomFormItem({ renderChild, node, bindTo, }) {
@@ -70,15 +70,13 @@ exports.ModalDialogMd = (0, metadata_helpers_1.createMetadata)({
70
70
  themeVars: (0, themeVars_1.parseScssVar)(ModalDialog_module_scss_1.default.themeVars),
71
71
  defaultThemeVars: Object.assign(Object.assign({}, (0, base_utils_1.paddingSubject)(COMP, { all: "$space-7" })), { [`backgroundColor-${COMP}`]: "$backgroundColor-primary", [`backgroundColor-overlay-${COMP}`]: "$backgroundColor-overlay", [`textColor-${COMP}`]: "$textColor-primary", [`borderRadius-${COMP}`]: "$borderRadius", [`fontFamily-${COMP}`]: "$fontFamily", [`maxWidth-${COMP}`]: "450px", [`marginBottom-title-${COMP}`]: "0" }),
72
72
  });
73
- exports.modalViewComponentRenderer = (0, renderers_1.createComponentRenderer)(COMP, exports.ModalDialogMd, ({ node, extractValue, className, renderChild, lookupEventHandler, registerComponentApi, layoutContext, }) => {
73
+ exports.modalViewComponentRenderer = (0, renderers_1.createComponentRenderer)(COMP, exports.ModalDialogMd, ({ node, contextVars, extractValue, className, renderChild, lookupEventHandler, registerComponentApi, layoutContext, }) => {
74
74
  var _a, _b;
75
- // gigantic hack: If the ModalDialog is not inside a ModalDialogFrame, wrap it in one
76
- // we do this through the layout context, render it through another render loop with the extra $param context var
77
- // (note the layoutContext and node on the MemoizedItem)
78
- // one solution would be to have a renderChild that can take a contextVars argument
75
+ // --- If the ModalDialog is not inside a ModalDialogFrame, wrap it in one.
79
76
  if (!(layoutContext === null || layoutContext === void 0 ? void 0 : layoutContext._insideModalFrame)) {
77
+ // --- Context variables are now directly available via contextVars parameter
80
78
  return ((0, jsx_runtime_1.jsx)(ModalDialogNative_1.ModalDialogFrame, { isInitiallyOpen: extractValue(node.when) !== undefined, registerComponentApi: registerComponentApi, onClose: lookupEventHandler("close"), onOpen: lookupEventHandler("open"), renderDialog: ({ openParams, ref }) => {
81
- return ((0, jsx_runtime_1.jsx)(container_helpers_1.MemoizedItem, { node: node, renderChild: renderChild, layoutContext: { _insideModalFrame: true }, contextVars: { $param: openParams === null || openParams === void 0 ? void 0 : openParams[0], $params: openParams } }));
79
+ return ((0, jsx_runtime_1.jsx)(container_helpers_1.MemoizedItem, { node: node, renderChild: renderChild, layoutContext: { _insideModalFrame: true }, contextVars: Object.assign(Object.assign({}, contextVars), { $param: openParams === null || openParams === void 0 ? void 0 : openParams[0], $params: openParams }) }));
82
80
  } }));
83
81
  }
84
82
  return ((0, jsx_runtime_1.jsx)(ModalDialogNative_1.ModalDialog, { className: className, fullScreen: extractValue.asOptionalBoolean((_a = node.props) === null || _a === void 0 ? void 0 : _a.fullScreen), title: extractValue((_b = node.props) === null || _b === void 0 ? void 0 : _b.title), closeButtonVisible: extractValue.asOptionalBoolean(node.props.closeButtonVisible), externalAnimation: extractValue.asOptionalBoolean(node.props.externalAnimation), children: renderChild(node.children, { type: "Stack" }) }));
@@ -63,6 +63,25 @@ fixtures_1.test.describe("Open/Close", () => {
63
63
  yield page.getByTestId("button").click();
64
64
  yield (0, fixtures_1.expect)(page.getByTestId("modal").getByRole("heading")).toHaveText("PARAM_VALUE");
65
65
  }));
66
+ (0, fixtures_1.test)("Preserves $item context variable from Table Column", (_a) => __awaiter(void 0, [_a], void 0, function* ({ page, initTestBed }) {
67
+ yield initTestBed(`
68
+ <Table data='{[
69
+ {id: 1, company: "Acme Corp", order: 1},
70
+ ]}'>
71
+ <Column>
72
+ <ModalDialog id="dialog" testId="dialog" title="{$item.company}">
73
+ <Text testId="modal-text">{JSON.stringify($item)}</Text>
74
+ </ModalDialog>
75
+ <Button testId="btn-{$itemIndex}" onClick="dialog.open()">{$item.company}</Button>
76
+ </Column>
77
+ </Table>
78
+ `);
79
+ // Test first row
80
+ yield page.getByTestId("btn-0").click();
81
+ const modal = page.getByTestId("dialog");
82
+ yield (0, fixtures_1.expect)(modal).toBeVisible();
83
+ yield (0, fixtures_1.expect)(modal).toContainText("Acme Corp");
84
+ }));
66
85
  (0, fixtures_1.test)("Declarative open/close", (_a) => __awaiter(void 0, [_a], void 0, function* ({ page, initTestBed }) {
67
86
  yield initTestBed(`
68
87
  <Fragment var.isOpen="{false}">
@@ -9,7 +9,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- const component_test_helpers_1 = require("../../testing/component-test-helpers");
13
12
  const fixtures_1 = require("../../testing/fixtures");
14
13
  fixtures_1.test.describe("smoke tests", { tag: "@smoke" }, () => {
15
14
  (0, fixtures_1.test)("displays menuitems after click", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
@@ -103,19 +102,26 @@ fixtures_1.test.describe("smoke tests", { tag: "@smoke" }, () => {
103
102
  yield (0, fixtures_1.expect)(page.getByRole("menuitem", { name: "Page 1" })).toBeVisible();
104
103
  yield (0, fixtures_1.expect)(page.getByRole("menuitem", { name: "inner page 2" })).not.toBeVisible();
105
104
  }));
106
- fixtures_1.test.fixme("nested initiallyExpanded works", component_test_helpers_1.SKIP_REASON.XMLUI_BUG("see https://github.com/radix-ui/primitives/issues/2551#issuecomment-2457236467 . The suggested workaround does not work for us, if you were to do it, you would see the hover effect not working for the inner most menu items."), (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
105
+ (0, fixtures_1.test)("nested initiallyExpanded works", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
107
106
  yield initTestBed(`
108
- <NavGroup label="Pages" initiallyExpanded="true">
109
- <NavLink label="Page 1" />
110
- <NavGroup label="subpages" initiallyExpanded="true">
111
- <NavLink label="inner page 2" />
112
- <NavLink label="inner page 3" />
107
+ <Stack testId="stack">
108
+ <NavGroup label="Pages" initiallyExpanded="true">
109
+ <NavLink label="Page 1" />
110
+ <NavGroup label="subpages" initiallyExpanded="true">
111
+ <NavLink label="inner page 2" />
112
+ <NavLink label="inner page 3" />
113
+ </NavGroup>
114
+ <NavLink label="Page 4" />
113
115
  </NavGroup>
114
- <NavLink label="Page 4" />
115
- </NavGroup>
116
+ </Stack>
116
117
  `);
117
- yield (0, fixtures_1.expect)(page.getByRole("menuitem", { name: "Page 1" })).toBeVisible();
118
- yield (0, fixtures_1.expect)(page.getByRole("menuitem", { name: "inner page 2" })).toBeVisible();
118
+ const stack = page.getByTestId("stack");
119
+ yield (0, fixtures_1.expect)(stack).toBeVisible();
120
+ const items = page.getByRole("menuitem");
121
+ yield (0, fixtures_1.expect)(items).toHaveCount(3);
122
+ (0, fixtures_1.expect)(items.nth(0)).toHaveText("Page 1");
123
+ (0, fixtures_1.expect)(items.nth(1)).toHaveText("subpages");
124
+ (0, fixtures_1.expect)(items.nth(2)).toHaveText("Page 4");
119
125
  }));
120
126
  (0, fixtures_1.test)("expands even without label", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
121
127
  yield initTestBed(`
@@ -210,3 +216,89 @@ fixtures_1.test.describe("icon props", () => {
210
216
  yield (0, fixtures_1.expect)(eye).not.toBeVisible();
211
217
  }));
212
218
  });
219
+ // =============================================================================
220
+ // DRAWER INTERACTION TESTS
221
+ // =============================================================================
222
+ fixtures_1.test.describe("Drawer Interaction", () => {
223
+ (0, fixtures_1.test)("clicking NavGroup toggle in drawer does not close drawer", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page, }) {
224
+ // Set small viewport to trigger drawer mode
225
+ yield page.setViewportSize({ width: 400, height: 600 });
226
+ yield initTestBed(`
227
+ <App layout="condensed">
228
+ <AppHeader testId="appHeader"/>
229
+ <NavPanel>
230
+ <NavGroup label="Pages">
231
+ <NavLink label="Page 1" to="/page1"/>
232
+ <NavLink label="Page 2" to="/page2"/>
233
+ </NavGroup>
234
+ </NavPanel>
235
+ <Pages fallbackPath="/">
236
+ <Page url="/">
237
+ <Text value="Home" />
238
+ </Page>
239
+ <Page url="/page1">
240
+ <Text value="Page 1" />
241
+ </Page>
242
+ <Page url="/page2">
243
+ <Text value="Page 2" />
244
+ </Page>
245
+ </Pages>
246
+ </App>
247
+ `);
248
+ // Open drawer by clicking hamburger button
249
+ const appHeader = page.getByTestId("appHeader");
250
+ const hamburgerButton = appHeader.locator('[data-part-id="hamburger"]').first();
251
+ yield hamburgerButton.click();
252
+ const dialog = page.getByRole("dialog");
253
+ yield (0, fixtures_1.expect)(dialog).toBeVisible();
254
+ // finst the first element in the dialog with a text of "Pages"
255
+ const navGroupToggle = dialog.getByRole("button", { name: "Pages" });
256
+ yield navGroupToggle.click();
257
+ yield page.waitForTimeout(200);
258
+ yield (0, fixtures_1.expect)(dialog).toBeVisible();
259
+ // There must be a text "Page1"
260
+ yield (0, fixtures_1.expect)(dialog).toContainText("Page 1");
261
+ yield (0, fixtures_1.expect)(dialog).toContainText("Page 2");
262
+ }));
263
+ (0, fixtures_1.test)("clicking NavLink in drawer closes drawer", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
264
+ // Set small viewport to trigger drawer mode
265
+ yield page.setViewportSize({ width: 400, height: 600 });
266
+ yield initTestBed(`
267
+ <App layout="condensed">
268
+ <AppHeader />
269
+ <NavPanel>
270
+ <NavGroup label="Pages">
271
+ <NavLink label="Page 1" to="/page1"/>
272
+ <NavLink label="Page 2" to="/page2"/>
273
+ </NavGroup>
274
+ </NavPanel>
275
+ <Pages fallbackPath="/">
276
+ <Page url="/">
277
+ <Text value="Home" />
278
+ </Page>
279
+ <Page url="/page1">
280
+ <Text value="Page 1 Content" />
281
+ </Page>
282
+ <Page url="/page2">
283
+ <Text value="Page 2" />
284
+ </Page>
285
+ </Pages>
286
+ </App>
287
+ `);
288
+ // Open drawer
289
+ const hamburgerButton = page.locator('[data-part-id="hamburger"]');
290
+ yield hamburgerButton.click();
291
+ const dialog = page.getByRole("dialog");
292
+ yield (0, fixtures_1.expect)(dialog).toBeVisible();
293
+ // Expand NavGroup
294
+ const navGroupToggle = dialog.getByRole("button", { name: "Pages" });
295
+ yield navGroupToggle.click();
296
+ yield page.waitForTimeout(200);
297
+ // Click a NavLink to navigate
298
+ yield dialog.getByRole("link", { name: "Page 1" }).click();
299
+ // Verify navigation occurred
300
+ yield (0, fixtures_1.expect)(page.getByText("Page 1 Content")).toBeVisible();
301
+ // Verify drawer is closed
302
+ yield (0, fixtures_1.expect)(dialog).not.toBeVisible();
303
+ }));
304
+ });
@@ -83,7 +83,12 @@ const ExpandableNavGroup = (0, react_1.forwardRef)(function ExpandableNavGroup(_
83
83
  }
84
84
  }, [pathname]);
85
85
  const toggleStyle = Object.assign(Object.assign({}, style), { "--nav-link-level": layoutIsVertical ? level : 0 });
86
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(NavLinkNative_1.NavLink, Object.assign({}, rest, { style: toggleStyle, onClick: () => setExpanded((prev) => !prev), icon: icon, to: to, disabled: disabled, "aria-expanded": expanded, children: [label, (0, jsx_runtime_1.jsx)("div", { style: { flex: 1 } }), (0, jsx_runtime_1.jsx)(IconNative_1.Icon, { name: expanded ? iconVerticalExpanded : iconVerticalCollapsed })] })), (0, jsx_runtime_1.jsx)("div", { "aria-hidden": !expanded, className: (0, classnames_1.default)(NavGroup_module_scss_1.default.groupContent, {
86
+ const handleClick = (e) => {
87
+ e.preventDefault();
88
+ e.stopPropagation();
89
+ setExpanded((prev) => !prev);
90
+ };
91
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(NavLinkNative_1.NavLink, Object.assign({}, rest, { style: toggleStyle, onClick: handleClick, icon: icon, to: to, disabled: disabled, "aria-expanded": expanded, children: [label, (0, jsx_runtime_1.jsx)("div", { style: { flex: 1 } }), (0, jsx_runtime_1.jsx)(IconNative_1.Icon, { name: expanded ? iconVerticalExpanded : iconVerticalCollapsed })] })), (0, jsx_runtime_1.jsx)("div", { "aria-hidden": !expanded, className: (0, classnames_1.default)(NavGroup_module_scss_1.default.groupContent, {
87
92
  [NavGroup_module_scss_1.default.expanded]: expanded,
88
93
  }), children: (0, jsx_runtime_1.jsx)("div", { className: NavGroup_module_scss_1.default.groupContentInner, ref: groupContentInnerRef, children: renderChild(node.children) }) })] }));
89
94
  });
@@ -340,6 +340,8 @@ fixtures_1.test.describe("Accessibility", () => {
340
340
  yield page.waitForTimeout(200);
341
341
  yield page.keyboard.press("ArrowDown");
342
342
  yield page.waitForTimeout(200);
343
+ yield page.keyboard.press("ArrowDown");
344
+ yield page.waitForTimeout(200);
343
345
  // Select with Enter
344
346
  yield page.keyboard.press("Enter");
345
347
  yield page.waitForTimeout(200);
@@ -445,7 +447,7 @@ fixtures_1.test.describe("Other Edge Cases", () => {
445
447
  // The object label should be visible with some string representation
446
448
  yield (0, fixtures_1.expect)(page.getByRole("option")).toHaveCount(2);
447
449
  }));
448
- (0, fixtures_1.test)("null values are handled gracefully", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page, createSelectDriver, }) {
450
+ (0, fixtures_1.test)("null values are handled gracefully", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page, createSelectDriver }) {
449
451
  yield initTestBed(`
450
452
  <Select>
451
453
  <Option label="{null}" value="null_label" />
@@ -6,7 +6,7 @@ const OptionContext_1 = require("./OptionContext");
6
6
  const react_1 = require("react");
7
7
  function HiddenOption(option) {
8
8
  const { label } = option;
9
- const { onOptionRemove, onOptionAdd } = (0, OptionContext_1.useOption)();
9
+ const { onOptionAdd } = (0, OptionContext_1.useOption)();
10
10
  const [node, setNode] = (0, react_1.useState)(null);
11
11
  const opt = (0, react_1.useMemo)(() => {
12
12
  var _a, _b;
@@ -14,7 +14,7 @@ function HiddenOption(option) {
14
14
  }, [option, node, label]);
15
15
  (0, react_1.useEffect)(() => {
16
16
  onOptionAdd(opt);
17
- return () => onOptionRemove(opt);
18
- }, [opt, onOptionAdd, onOptionRemove]);
17
+ // Don't remove options when component unmounts - they should persist
18
+ }, [opt, onOptionAdd]);
19
19
  return ((0, jsx_runtime_1.jsx)("div", { ref: (el) => setNode(el), style: { display: "none" }, children: option.children }));
20
20
  }
@@ -11,7 +11,6 @@ const themeVars_1 = require("../../components-core/theming/themeVars");
11
11
  const metadata_helpers_1 = require("../metadata-helpers");
12
12
  const container_helpers_1 = require("../container-helpers");
13
13
  const SelectNative_1 = require("./SelectNative");
14
- const react_select_1 = require("@radix-ui/react-select");
15
14
  const COMP = "Select";
16
15
  exports.SelectMd = (0, metadata_helpers_1.createMetadata)({
17
16
  status: "stable",
@@ -115,7 +114,7 @@ exports.selectComponentRenderer = (0, renderers_1.createComponentRenderer)(COMP,
115
114
  const multiSelect = extractValue.asOptionalBoolean(node.props.multiSelect);
116
115
  const searchable = extractValue.asOptionalBoolean(node.props.searchable);
117
116
  const isControlled = node.props.value !== undefined;
118
- return ((0, jsx_runtime_1.jsx)(SelectNative_1.Select, { multiSelect: multiSelect, className: className, inProgress: extractValue.asOptionalBoolean(node.props.inProgress), inProgressNotificationMessage: extractValue.asOptionalString(node.props.inProgressNotificationMessage), readOnly: extractValue.asOptionalBoolean(node.props.readOnly), updateState: isControlled ? undefined : updateState, searchable: searchable, initialValue: extractValue(node.props.initialValue), value: isControlled ? extractValue(node.props.value) : state === null || state === void 0 ? void 0 : state.value, autoFocus: extractValue.asOptionalBoolean(node.props.autoFocus), enabled: extractValue.asOptionalBoolean(node.props.enabled), placeholder: extractValue.asOptionalString(node.props.placeholder), validationStatus: extractValue(node.props.validationStatus), onDidChange: lookupEventHandler("didChange"), onFocus: lookupEventHandler("gotFocus"), onBlur: lookupEventHandler("lostFocus"), registerComponentApi: registerComponentApi, emptyListTemplate: renderChild(node.props.emptyListTemplate), dropdownHeight: extractValue(node.props.dropdownHeight), required: extractValue.asOptionalBoolean(node.props.required), valueRenderer: node.props.valueTemplate
117
+ return ((0, jsx_runtime_1.jsx)(SelectNative_1.Select, { multiSelect: multiSelect, className: className, inProgress: extractValue.asOptionalBoolean(node.props.inProgress), inProgressNotificationMessage: extractValue.asOptionalString(node.props.inProgressNotificationMessage), readOnly: extractValue.asOptionalBoolean(node.props.readOnly), updateState: isControlled ? undefined : updateState, searchable: searchable, initialValue: extractValue(node.props.initialValue), value: state === null || state === void 0 ? void 0 : state.value, autoFocus: extractValue.asOptionalBoolean(node.props.autoFocus), enabled: extractValue.asOptionalBoolean(node.props.enabled), placeholder: extractValue.asOptionalString(node.props.placeholder), validationStatus: extractValue(node.props.validationStatus), onDidChange: lookupEventHandler("didChange"), onFocus: lookupEventHandler("gotFocus"), onBlur: lookupEventHandler("lostFocus"), registerComponentApi: registerComponentApi, emptyListTemplate: renderChild(node.props.emptyListTemplate), dropdownHeight: extractValue(node.props.dropdownHeight), required: extractValue.asOptionalBoolean(node.props.required), valueRenderer: node.props.valueTemplate
119
118
  ? (item, removeItem) => {
120
119
  return ((0, jsx_runtime_1.jsx)(container_helpers_1.MemoizedItem, { contextVars: {
121
120
  $itemContext: { removeItem },
@@ -126,7 +125,7 @@ exports.selectComponentRenderer = (0, renderers_1.createComponentRenderer)(COMP,
126
125
  return ((0, jsx_runtime_1.jsx)(container_helpers_1.MemoizedItem, { node: node.props.optionTemplate, item: item, contextVars: {
127
126
  $selectedValue: val,
128
127
  $inTrigger: inTrigger,
129
- }, renderChild: (...args) => multiSelect || searchable ? (renderChild(...args)) : ((0, jsx_runtime_1.jsx)(react_select_1.SelectItemText, { children: renderChild(...args) })) }));
128
+ }, renderChild: renderChild }));
130
129
  }
131
130
  : undefined, children: renderChild(node.children) }));
132
131
  });
@@ -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, }) {