@mezzanine-ui/react 1.0.0-rc.6 → 1.0.0-rc.7

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 (61) hide show
  1. package/AutoComplete/AutoComplete.d.ts +25 -9
  2. package/AutoComplete/AutoComplete.js +84 -17
  3. package/AutoComplete/AutoCompleteInside.d.ts +54 -0
  4. package/AutoComplete/AutoCompleteInside.js +17 -0
  5. package/AutoComplete/useAutoCompleteKeyboard.d.ts +2 -1
  6. package/AutoComplete/useAutoCompleteKeyboard.js +4 -1
  7. package/Breadcrumb/BreadcrumbDropdown.d.ts +1 -1
  8. package/Breadcrumb/BreadcrumbOverflowMenuDropdown.d.ts +1 -1
  9. package/Breadcrumb/typings.d.ts +1 -1
  10. package/COMPONENTS.md +2 -2
  11. package/Checkbox/Checkbox.js +24 -3
  12. package/Cropper/Cropper.d.ts +1 -1
  13. package/Description/Description.d.ts +1 -1
  14. package/Description/Description.js +1 -1
  15. package/Description/DescriptionTitle.d.ts +6 -1
  16. package/Description/DescriptionTitle.js +2 -2
  17. package/Drawer/Drawer.d.ts +39 -34
  18. package/Drawer/Drawer.js +33 -35
  19. package/Dropdown/Dropdown.d.ts +16 -1
  20. package/Dropdown/Dropdown.js +156 -9
  21. package/Dropdown/DropdownItem.d.ts +26 -2
  22. package/Dropdown/DropdownItem.js +91 -43
  23. package/Dropdown/DropdownItemCard.d.ts +3 -2
  24. package/Dropdown/DropdownItemCard.js +8 -5
  25. package/Dropdown/dropdownKeydownHandler.d.ts +6 -0
  26. package/Dropdown/dropdownKeydownHandler.js +14 -7
  27. package/FilterArea/Filter.d.ts +25 -2
  28. package/FilterArea/Filter.js +23 -0
  29. package/FilterArea/FilterArea.d.ts +43 -4
  30. package/FilterArea/FilterArea.js +35 -2
  31. package/FilterArea/FilterLine.d.ts +19 -0
  32. package/FilterArea/FilterLine.js +19 -0
  33. package/Input/SpinnerButton/SpinnerButton.js +1 -1
  34. package/Modal/Modal.d.ts +22 -86
  35. package/Modal/Modal.js +4 -2
  36. package/Modal/ModalBodyForVerification.js +3 -1
  37. package/NotificationCenter/NotificationCenter.d.ts +21 -9
  38. package/NotificationCenter/NotificationCenter.js +22 -10
  39. package/NotificationCenter/NotificationCenterDrawer.d.ts +52 -1
  40. package/NotificationCenter/NotificationCenterDrawer.js +2 -2
  41. package/OverflowTooltip/OverflowTooltip.js +46 -5
  42. package/PageFooter/PageFooter.js +6 -14
  43. package/Pagination/PaginationPageSize.js +1 -1
  44. package/README.md +34 -4
  45. package/Radio/Radio.js +16 -2
  46. package/Table/Table.js +1 -1
  47. package/TimePicker/TimePicker.js +1 -1
  48. package/TimeRangePicker/TimeRangePicker.js +1 -1
  49. package/Toggle/Toggle.d.ts +1 -1
  50. package/Toggle/Toggle.js +1 -1
  51. package/Upload/Upload.d.ts +13 -7
  52. package/Upload/Upload.js +55 -20
  53. package/Upload/UploadItem.js +4 -1
  54. package/Upload/UploadPictureCard.d.ts +5 -0
  55. package/Upload/UploadPictureCard.js +8 -5
  56. package/Upload/Uploader.d.ts +32 -31
  57. package/Upload/Uploader.js +10 -9
  58. package/index.d.ts +3 -3
  59. package/index.js +1 -1
  60. package/llms.txt +128 -9
  61. package/package.json +5 -4
@@ -33,7 +33,7 @@ function truncateArrayDepth(input, maxDepth = 3, warn = true) {
33
33
  // Stop going deeper once maximum depth is reached, remove children
34
34
  return options.map(({ children: _children, ...option }) => option);
35
35
  }
36
- return options.map(option => {
36
+ return options.map((option) => {
37
37
  if (!option.children)
38
38
  return option;
39
39
  return {
@@ -65,13 +65,15 @@ function truncateArrayDepth(input, maxDepth = 3, warn = true) {
65
65
  return truncate(input);
66
66
  }
67
67
  function DropdownItem(props) {
68
- const { activeIndex, disabled = false, listboxId, listboxLabel, mode = 'single', options, toggleCheckedOnClick, value, type, maxHeight, actionConfig, onHover, onSelect, followText, headerContent, status, loadingText, emptyText, emptyIcon, loadingPosition = 'full', onReachBottom, onLeaveBottom, onScroll, scrollbarDefer = true, scrollbarDisabled = false, scrollbarMaxWidth, scrollbarOptions, } = props;
68
+ const { activeIndex, keyboardActiveIndex, disabled = false, listboxId, listboxLabel, mode = 'single', options, toggleCheckedOnClick, value, type, maxHeight, minWidth, actionConfig, onHover, onSelect, followText, headerContent, status, loadingText, emptyText, emptyIcon, loadingPosition = 'full', onReachBottom, onLeaveBottom, onScroll, scrollbarDefer = true, scrollbarDisabled = false, scrollbarMaxWidth, scrollbarOptions, } = props;
69
+ const { expandedNodes: expandedNodesProp, onToggleExpand: onToggleExpandProp, } = props;
69
70
  const optionsContent = truncateArrayDepth(options, 3);
70
71
  const listRef = useRef(null);
71
72
  const listWrapperRef = useRef(null);
72
73
  const viewportRef = useRef(null);
73
74
  const wasAtBottomRef = useRef(false);
74
- const [expandedNodes, setExpandedNodes] = useState(new Set());
75
+ const [internalExpandedNodes, setInternalExpandedNodes] = useState(new Set());
76
+ const expandedNodes = expandedNodesProp !== null && expandedNodesProp !== void 0 ? expandedNodesProp : internalExpandedNodes;
75
77
  const hasActions = Boolean(actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.showActions);
76
78
  const hasHeader = Boolean(headerContent);
77
79
  const shouldUseScrollbar = maxHeight && !scrollbarDisabled;
@@ -79,7 +81,11 @@ function DropdownItem(props) {
79
81
  const [actionRef, actionHeight] = useElementHeight(hasActions && !!maxHeight);
80
82
  const [headerRef, headerHeight] = useElementHeight(hasHeader && !!maxHeight);
81
83
  const toggleExpand = useCallback((optionId) => {
82
- setExpandedNodes((prev) => {
84
+ if (onToggleExpandProp) {
85
+ onToggleExpandProp(optionId);
86
+ return;
87
+ }
88
+ setInternalExpandedNodes((prev) => {
83
89
  const next = new Set(prev);
84
90
  if (next.has(optionId)) {
85
91
  next.delete(optionId);
@@ -89,7 +95,7 @@ function DropdownItem(props) {
89
95
  }
90
96
  return next;
91
97
  });
92
- }, []);
98
+ }, [onToggleExpandProp]);
93
99
  const visibleShortcutOptions = useMemo(() => {
94
100
  const result = [];
95
101
  const collectDefault = (optionList) => {
@@ -164,10 +170,10 @@ function DropdownItem(props) {
164
170
  });
165
171
  if (!mainToken)
166
172
  return false;
167
- if (requireMeta !== event.metaKey
168
- || requireCtrl !== event.ctrlKey
169
- || requireAlt !== event.altKey
170
- || requireShift !== event.shiftKey) {
173
+ if (requireMeta !== event.metaKey ||
174
+ requireCtrl !== event.ctrlKey ||
175
+ requireAlt !== event.altKey ||
176
+ requireShift !== event.shiftKey) {
171
177
  return false;
172
178
  }
173
179
  const mainCode = keycode(mainToken);
@@ -179,7 +185,8 @@ function DropdownItem(props) {
179
185
  return true;
180
186
  }
181
187
  const eventKeyName = keycode(eventCode);
182
- return typeof eventKeyName === 'string' && eventKeyName.toLowerCase() === mainToken;
188
+ return (typeof eventKeyName === 'string' &&
189
+ eventKeyName.toLowerCase() === mainToken);
183
190
  }, []);
184
191
  const renderGroupedOptions = (optionList, startIndex) => {
185
192
  let currentIndex = startIndex;
@@ -190,10 +197,13 @@ function DropdownItem(props) {
190
197
  if (hasChildren) {
191
198
  groupElements.push(jsx(Typography, { variant: "body", className: dropdownClasses.groupLabel, children: groupOption.name }, groupOption.id));
192
199
  (_a = groupOption.children) === null || _a === void 0 ? void 0 : _a.forEach((option) => {
193
- var _a, _b, _c;
200
+ var _a, _b, _c, _d;
194
201
  currentIndex += 1;
195
202
  const optionIndex = currentIndex;
196
- const isActive = optionIndex === activeIndex;
203
+ const isActive = optionIndex ===
204
+ (keyboardActiveIndex !== undefined
205
+ ? keyboardActiveIndex
206
+ : activeIndex);
197
207
  const isSelected = Array.isArray(value)
198
208
  ? value.includes(option.id)
199
209
  : value === option.id;
@@ -204,7 +214,7 @@ function DropdownItem(props) {
204
214
  if (disabled)
205
215
  return;
206
216
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
207
- }, checkSite: "none", validate: (_b = option.validate) !== null && _b !== void 0 ? _b : 'default', onMouseEnter: () => onHover === null || onHover === void 0 ? void 0 : onHover(optionIndex), showUnderline: (_c = option.showUnderline) !== null && _c !== void 0 ? _c : false, appendContent: shortcutText }, option.id));
217
+ }, checkSite: (_b = option === null || option === void 0 ? void 0 : option.checkSite) !== null && _b !== void 0 ? _b : 'suffix', validate: (_c = option.validate) !== null && _c !== void 0 ? _c : 'default', onMouseEnter: () => onHover === null || onHover === void 0 ? void 0 : onHover(optionIndex), showUnderline: (_d = option.showUnderline) !== null && _d !== void 0 ? _d : false, appendContent: shortcutText }, option.id));
208
218
  });
209
219
  }
210
220
  return groupElements;
@@ -230,37 +240,49 @@ function DropdownItem(props) {
230
240
  }, []);
231
241
  const renderTreeOptions = (optionList, depth, startIndex) => {
232
242
  let currentIndex = startIndex;
233
- const selectedIds = Array.isArray(value) ? value.map((id) => String(id)) : value ? [String(value)] : [];
243
+ const selectedIds = Array.isArray(value)
244
+ ? value.map((id) => String(id))
245
+ : value
246
+ ? [String(value)]
247
+ : [];
234
248
  const elements = (optionList !== null && optionList !== void 0 ? optionList : []).flatMap((option) => {
235
- var _a, _b, _c;
249
+ var _a, _b, _c, _d;
236
250
  currentIndex += 1;
237
251
  const optionIndex = currentIndex;
238
252
  const level = Math.min(depth, 2);
239
- const isActive = optionIndex === activeIndex;
253
+ const isActive = optionIndex ===
254
+ (keyboardActiveIndex !== undefined ? keyboardActiveIndex : activeIndex);
240
255
  const hasChildren = Boolean(option.children && option.children.length > 0);
241
256
  const isExpanded = hasChildren && expandedNodes.has(option.id);
242
257
  let prependIcon = undefined;
243
258
  if (hasChildren && level !== 2) {
244
259
  prependIcon = isExpanded ? CaretDownIcon : CaretRightIcon;
245
260
  }
246
- const checkSite = option.showCheckbox ? 'prefix' : 'none';
261
+ const checkSite = option.showCheckbox
262
+ ? 'prefix'
263
+ : ((_a = option.checkSite) !== null && _a !== void 0 ? _a : (mode === 'single' ? 'suffix' : 'none'));
247
264
  const shortcutText = option.shortcutText
248
265
  ? option.shortcutText
249
- : shortcutTextHandler((_a = option.shortcutKeys) !== null && _a !== void 0 ? _a : []);
266
+ : shortcutTextHandler((_b = option.shortcutKeys) !== null && _b !== void 0 ? _b : []);
250
267
  const selectionState = hasChildren && mode === 'multiple'
251
268
  ? calculateNodeSelectionState(option, selectedIds)
252
269
  : {
253
270
  checked: selectedIds.includes(String(option.id)),
254
271
  indeterminate: false,
255
272
  };
256
- const resolvedToggleCheckedOnClick = toggleCheckedOnClick !== null && toggleCheckedOnClick !== void 0 ? toggleCheckedOnClick : !(hasChildren
257
- && type === 'tree'
258
- && mode === 'multiple'
259
- && option.showCheckbox);
260
- const card = (jsx(DropdownItemCard, { active: isActive, checked: selectionState.checked, indeterminate: selectionState.indeterminate, disabled: disabled, id: `${listboxId}-option-${optionIndex}`, label: option.name, level: level, mode: mode, name: option.name, toggleCheckedOnClick: resolvedToggleCheckedOnClick, onClick: () => {
273
+ const resolvedToggleCheckedOnClick = toggleCheckedOnClick !== null && toggleCheckedOnClick !== void 0 ? toggleCheckedOnClick : !(hasChildren &&
274
+ type === 'tree' &&
275
+ mode === 'multiple' &&
276
+ option.showCheckbox);
277
+ const card = (jsx(DropdownItemCard, { active: isActive, checked: selectionState.checked, className: !hasChildren && level === 1
278
+ ? dropdownClasses.cardLeafLevel1
279
+ : undefined, indeterminate: selectionState.indeterminate, disabled: disabled, id: `${listboxId}-option-${optionIndex}`, label: option.name, level: level, mode: mode, name: option.name, toggleCheckedOnClick: resolvedToggleCheckedOnClick, onClick: () => {
261
280
  if (disabled)
262
281
  return;
263
- if (hasChildren && type === 'tree' && mode === 'multiple' && option.showCheckbox) {
282
+ if (hasChildren &&
283
+ type === 'tree' &&
284
+ mode === 'multiple' &&
285
+ option.showCheckbox) {
264
286
  toggleExpand(option.id);
265
287
  }
266
288
  else if (hasChildren && type === 'tree') {
@@ -270,7 +292,10 @@ function DropdownItem(props) {
270
292
  // In `tree` + `multiple` mode, `DropdownItemCard` already triggers selection via
271
293
  // `onCheckedChange` when row is clicked (it toggles checked first, then calls `onClick`),
272
294
  // so calling `onSelect` here would cause it to fire twice for leaf nodes.
273
- if (!(type === 'tree' && mode === 'multiple')) {
295
+ // In `multiple` mode, row click also triggers `onCheckedChange` when
296
+ // `toggleCheckedOnClick` is enabled, so avoid firing `onSelect` twice.
297
+ if (!(type === 'tree' && mode === 'multiple') &&
298
+ !(mode === 'multiple' && resolvedToggleCheckedOnClick)) {
274
299
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
275
300
  }
276
301
  }
@@ -278,7 +303,7 @@ function DropdownItem(props) {
278
303
  if (!disabled) {
279
304
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
280
305
  }
281
- }, followText: followText, checkSite: checkSite, onMouseEnter: () => onHover === null || onHover === void 0 ? void 0 : onHover(optionIndex), prependIcon: prependIcon, showUnderline: (_b = option.showUnderline) !== null && _b !== void 0 ? _b : false, validate: (_c = option.validate) !== null && _c !== void 0 ? _c : 'default', appendContent: shortcutText }, option.id));
306
+ }, followText: followText, checkSite: checkSite, onMouseEnter: () => onHover === null || onHover === void 0 ? void 0 : onHover(optionIndex), prependIcon: prependIcon, showUnderline: (_c = option.showUnderline) !== null && _c !== void 0 ? _c : false, validate: (_d = option.validate) !== null && _d !== void 0 ? _d : 'default', appendContent: shortcutText }, option.id));
282
307
  if (hasChildren && isExpanded && type === 'tree') {
283
308
  const childResult = renderTreeOptions(option.children, depth + 1, currentIndex);
284
309
  currentIndex = childResult.nextIndex;
@@ -297,11 +322,12 @@ function DropdownItem(props) {
297
322
  const isSelected = Array.isArray(value)
298
323
  ? value.includes(option.id)
299
324
  : value === option.id;
300
- const isActive = optionIndex === activeIndex;
325
+ const isActive = optionIndex ===
326
+ (keyboardActiveIndex !== undefined ? keyboardActiveIndex : activeIndex);
301
327
  const shortcutText = option.shortcutText
302
328
  ? option.shortcutText
303
329
  : shortcutTextHandler((_a = option.shortcutKeys) !== null && _a !== void 0 ? _a : []);
304
- const checkSite = (_b = option === null || option === void 0 ? void 0 : option.checkSite) !== null && _b !== void 0 ? _b : 'none';
330
+ const checkSite = (_b = option === null || option === void 0 ? void 0 : option.checkSite) !== null && _b !== void 0 ? _b : 'suffix';
305
331
  return (jsx(DropdownItemCard, { followText: followText, active: isActive, checked: isSelected, disabled: disabled, id: `${listboxId}-option-${optionIndex}`, label: option.name, mode: mode, name: option.name, toggleCheckedOnClick: toggleCheckedOnClick, onClick: () => {
306
332
  if (disabled)
307
333
  return;
@@ -328,13 +354,17 @@ function DropdownItem(props) {
328
354
  // Show bottom loading when status is loading and loadingPosition is bottom
329
355
  const shouldShowBottomLoading = status === 'loading' && loadingPosition === 'bottom';
330
356
  const listStyle = useMemo(() => {
331
- if (!maxHeight) {
332
- return undefined;
357
+ const styles = {};
358
+ if (maxHeight) {
359
+ styles.maxHeight =
360
+ typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight;
333
361
  }
334
- return {
335
- maxHeight: typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight,
336
- };
337
- }, [maxHeight]);
362
+ if (minWidth !== undefined) {
363
+ styles['--mzn-dropdown-list-min-width'] =
364
+ typeof minWidth === 'number' ? `${minWidth}px` : minWidth;
365
+ }
366
+ return Object.keys(styles).length > 0 ? styles : undefined;
367
+ }, [maxHeight, minWidth]);
338
368
  const listWrapperStyle = useMemo(() => {
339
369
  if (!maxHeight) {
340
370
  return undefined;
@@ -358,7 +388,8 @@ function DropdownItem(props) {
358
388
  if (event.repeat)
359
389
  return;
360
390
  const targetOption = visibleShortcutOptions.find((option) => {
361
- if (!Array.isArray(option.shortcutKeys) || option.shortcutKeys.length === 0) {
391
+ if (!Array.isArray(option.shortcutKeys) ||
392
+ option.shortcutKeys.length === 0) {
362
393
  return false;
363
394
  }
364
395
  return option.shortcutKeys.some((shortcut) => matchShortcut(event, shortcut));
@@ -367,7 +398,9 @@ function DropdownItem(props) {
367
398
  return;
368
399
  event.preventDefault();
369
400
  event.stopPropagation();
370
- if (type === 'tree' && targetOption.children && targetOption.children.length > 0) {
401
+ if (type === 'tree' &&
402
+ targetOption.children &&
403
+ targetOption.children.length > 0) {
371
404
  toggleExpand(targetOption.id);
372
405
  return;
373
406
  }
@@ -377,7 +410,14 @@ function DropdownItem(props) {
377
410
  return () => {
378
411
  listElement.removeEventListener('keydown', handleKeyDown);
379
412
  };
380
- }, [disabled, matchShortcut, onSelect, type, toggleExpand, visibleShortcutOptions]);
413
+ }, [
414
+ disabled,
415
+ matchShortcut,
416
+ onSelect,
417
+ type,
418
+ toggleExpand,
419
+ visibleShortcutOptions,
420
+ ]);
381
421
  const handleViewportReady = useCallback((viewport) => {
382
422
  viewportRef.current = viewport;
383
423
  listWrapperRef.current = viewport;
@@ -416,7 +456,9 @@ function DropdownItem(props) {
416
456
  return;
417
457
  }
418
458
  const listWrapperElement = listWrapperRef.current;
419
- if (!listWrapperElement || !maxHeight || (!onReachBottom && !onLeaveBottom && !onScroll)) {
459
+ if (!listWrapperElement ||
460
+ !maxHeight ||
461
+ (!onReachBottom && !onLeaveBottom && !onScroll)) {
420
462
  return;
421
463
  }
422
464
  // Initialize wasAtBottom state by checking current position
@@ -453,7 +495,8 @@ function DropdownItem(props) {
453
495
  };
454
496
  }, [maxHeight, onReachBottom, onLeaveBottom, onScroll, shouldUseScrollbar]);
455
497
  const scrollbarEvents = useMemo(() => {
456
- if (!shouldUseScrollbar || (!onReachBottom && !onLeaveBottom && !onScroll)) {
498
+ if (!shouldUseScrollbar ||
499
+ (!onReachBottom && !onLeaveBottom && !onScroll)) {
457
500
  return undefined;
458
501
  }
459
502
  return {
@@ -479,10 +522,15 @@ function DropdownItem(props) {
479
522
  wasAtBottomRef.current = isAtBottom;
480
523
  },
481
524
  };
482
- }, [getIsAtBottom, shouldUseScrollbar, onReachBottom, onLeaveBottom, onScroll]);
483
- return (jsxs("ul", { "aria-label": listboxLabel || (optionsContent.length === 0 ? 'Dropdown options' : undefined), className: dropdownClasses.list, id: listboxId, ref: listRef, role: "listbox", style: listStyle, tabIndex: -1, children: [hasHeader && (jsx("li", { className: dropdownClasses.listHeader, role: "presentation", ref: headerRef, children: jsx("div", { className: dropdownClasses.listHeaderInner, children: headerContent }) })), maxHeight
484
- ? (shouldUseScrollbar ? (jsx(Scrollbar, { className: dropdownClasses.listWrapper, defer: scrollbarDefer, disabled: false, events: scrollbarEvents, maxHeight: listWrapperStyle === null || listWrapperStyle === void 0 ? void 0 : listWrapperStyle.maxHeight, maxWidth: scrollbarMaxWidth, onViewportReady: handleViewportReady, options: scrollbarOptions, children: shouldShowFullStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (jsxs(Fragment, { children: [renderedOptions, shouldShowBottomLoading && (jsx("li", { className: dropdownClasses.loadingMore, "aria-live": "polite", role: "status", children: jsx(DropdownStatus, { status: "loading", loadingText: loadingText }) }))] })) })) : (jsx("div", { ref: listWrapperRef, className: dropdownClasses.listWrapper, style: listWrapperStyle, children: shouldShowFullStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (jsxs(Fragment, { children: [renderedOptions, shouldShowBottomLoading && (jsx("li", { className: dropdownClasses.loadingMore, "aria-live": "polite", role: "status", children: jsx(DropdownStatus, { status: "loading", loadingText: loadingText }) }))] })) })))
485
- : shouldShowFullStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (jsxs(Fragment, { children: [renderedOptions, shouldShowBottomLoading && (jsx("li", { className: dropdownClasses.loadingMore, "aria-live": "polite", role: "status", children: jsx(DropdownStatus, { status: "loading", loadingText: loadingText }) }))] })), hasActions && (jsx("div", { ref: actionRef, children: jsx(DropdownAction, { ...actionConfig }) }))] }));
525
+ }, [
526
+ getIsAtBottom,
527
+ shouldUseScrollbar,
528
+ onReachBottom,
529
+ onLeaveBottom,
530
+ onScroll,
531
+ ]);
532
+ return (jsxs("ul", { "aria-label": listboxLabel ||
533
+ (optionsContent.length === 0 ? 'Dropdown options' : undefined), className: dropdownClasses.list, id: listboxId, ref: listRef, role: "listbox", style: listStyle, tabIndex: -1, children: [hasHeader && (jsx("li", { className: dropdownClasses.listHeader, role: "presentation", ref: headerRef, children: jsx("div", { className: dropdownClasses.listHeaderInner, children: headerContent }) })), maxHeight ? (shouldUseScrollbar ? (jsx(Scrollbar, { className: dropdownClasses.listWrapper, defer: scrollbarDefer, disabled: false, events: scrollbarEvents, maxHeight: listWrapperStyle === null || listWrapperStyle === void 0 ? void 0 : listWrapperStyle.maxHeight, maxWidth: scrollbarMaxWidth, onViewportReady: handleViewportReady, options: scrollbarOptions, children: shouldShowFullStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (jsxs(Fragment, { children: [renderedOptions, shouldShowBottomLoading && (jsx("li", { className: dropdownClasses.loadingMore, "aria-live": "polite", role: "status", children: jsx(DropdownStatus, { status: "loading", loadingText: loadingText }) }))] })) })) : (jsx("div", { ref: listWrapperRef, className: dropdownClasses.listWrapper, style: listWrapperStyle, children: shouldShowFullStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (jsxs(Fragment, { children: [renderedOptions, shouldShowBottomLoading && (jsx("li", { className: dropdownClasses.loadingMore, "aria-live": "polite", role: "status", children: jsx(DropdownStatus, { status: "loading", loadingText: loadingText }) }))] })) }))) : shouldShowFullStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (jsxs(Fragment, { children: [renderedOptions, shouldShowBottomLoading && (jsx("li", { className: dropdownClasses.loadingMore, "aria-live": "polite", role: "status", children: jsx(DropdownStatus, { status: "loading", loadingText: loadingText }) }))] })), hasActions && (jsx("div", { ref: actionRef, children: jsx(DropdownAction, { ...actionConfig }) }))] }));
486
534
  }
487
535
 
488
536
  export { DropdownItem as default };
@@ -3,8 +3,9 @@ import { type IconDefinition } from '@mezzanine-ui/icons';
3
3
  export interface DropdownItemCardProps {
4
4
  /**
5
5
  * Whether the option is currently active (highlighted by keyboard navigation).
6
- * This controls the aria-selected attribute according to W3C ARIA spec.
7
- * When an option is referenced by aria-activedescendant, it should have aria-selected="true".
6
+ * Used to apply visual highlight and drive the `--active` CSS modifier.
7
+ * Note: keyboard focus position is communicated to screen readers via `aria-activedescendant`
8
+ * on the trigger element, not via `aria-selected` on the option itself.
8
9
  */
9
10
  active?: boolean;
10
11
  /**
@@ -57,8 +57,10 @@ function DropdownItemCard(props) {
57
57
  ];
58
58
  }, [cardLabel, followText]);
59
59
  const showPrependContent = useMemo(() => {
60
- return prependIcon || (checkSite === 'prefix' && mode === 'multiple');
61
- }, [prependIcon, checkSite, mode]);
60
+ return (prependIcon ||
61
+ (checkSite === 'prefix' && mode === 'multiple') ||
62
+ (checkSite === 'prefix' && mode === 'single' && isChecked));
63
+ }, [prependIcon, checkSite, mode, isChecked]);
62
64
  const showAppendContent = useMemo(() => {
63
65
  return appendContent || appendIcon || (checkSite === 'suffix' && isChecked);
64
66
  }, [appendContent, appendIcon, checkSite, isChecked]);
@@ -105,11 +107,12 @@ function DropdownItemCard(props) {
105
107
  return;
106
108
  if (event.key === 'Enter' || event.key === ' ') {
107
109
  event.preventDefault();
108
- onClick === null || onClick === void 0 ? void 0 : onClick();
110
+ handleClick();
109
111
  }
110
112
  };
111
- return (jsxs(Fragment, { children: [jsx("li", { ...(labelId ? { 'aria-labelledby': labelId } : {}), ...(ariaLabel ? { 'aria-label': ariaLabel } : {}), "aria-selected": active, className: cx(dropdownClasses.card, dropdownClasses.cardLevel(level), {
113
+ return (jsxs(Fragment, { children: [jsx("li", { ...(labelId ? { 'aria-labelledby': labelId } : {}), ...(ariaLabel ? { 'aria-label': ariaLabel } : {}), "aria-selected": isChecked, className: cx(dropdownClasses.card, dropdownClasses.cardLevel(level), {
112
114
  [dropdownClasses.cardActive]: active || isChecked,
115
+ [dropdownClasses.cardKeyboardActive]: active,
113
116
  [dropdownClasses.cardDisabled]: disabled,
114
117
  [dropdownClasses.cardDanger]: validate === 'danger',
115
118
  }, className), id: id, role: "option", tabIndex: -1, onMouseEnter: onMouseEnter, onClick: handleClick, onKeyDown: handleKeyDown, children: jsxs("div", { className: dropdownClasses.cardContainer, children: [showPrependContent && (jsxs("div", { className: dropdownClasses.cardPrependContent, children: [prependIcon && jsx(Icon, { icon: prependIcon, color: iconColor }), checkSite === 'prefix' && mode === 'multiple' && (jsx(Checkbox, { checked: isChecked, disabled: disabled, indeterminate: indeterminate, onChange: handleCheckboxChange, ...(onCheckedChange
@@ -117,7 +120,7 @@ function DropdownItemCard(props) {
117
120
  onClick: (event) => event.stopPropagation(),
118
121
  onMouseDown: (event) => event.stopPropagation(),
119
122
  }
120
- : {}) }))] })), jsxs("div", { className: dropdownClasses.cardBody, children: [cardLabel &&
123
+ : {}) })), checkSite === 'prefix' && mode === 'single' && isChecked && (jsx(Icon, { icon: CheckedIcon, color: appendIconColor, size: 16 }))] })), jsxs("div", { className: dropdownClasses.cardBody, children: [cardLabel &&
121
124
  renderHighlightedText(labelParts, dropdownClasses.cardTitle, labelId), subTitleParts.length > 0 &&
122
125
  renderHighlightedText(subTitleParts, dropdownClasses.cardDescription)] }), showAppendContent && (jsxs("div", { className: dropdownClasses.cardAppendContent, children: [appendContent && (jsx(Typography, { color: "text-neutral-light", children: appendContent })), appendIcon && jsx(Icon, { icon: appendIcon, color: iconColor }), checkSite === 'suffix' && isChecked && (jsx(Icon, { icon: CheckedIcon, color: appendIconColor, size: 16 }))] }))] }) }), showUnderline && (jsx("li", { role: "presentation", "aria-hidden": "true", children: jsx(Separator, { orientation: "horizontal", className: dropdownClasses.cardUnderline }) }))] }));
123
126
  }
@@ -11,6 +11,12 @@ export declare function createDropdownKeydownHandler(params: {
11
11
  open: boolean;
12
12
  options: DropdownOption[];
13
13
  setActiveIndex: Dispatch<SetStateAction<number | null>>;
14
+ /**
15
+ * Optional setter for keyboard-only active index.
16
+ * When provided, it is updated alongside `setActiveIndex` on arrow key navigation,
17
+ * and cleared on Escape / directional keys that exit the list.
18
+ */
19
+ setKeyboardActiveIndex?: Dispatch<SetStateAction<number | null>>;
14
20
  setListboxHasVisualFocus: (focus: boolean) => void;
15
21
  setOpen: (open: boolean) => void;
16
22
  }): (e: React.KeyboardEvent<HTMLInputElement>) => void;
@@ -3,7 +3,7 @@
3
3
  * Keeps logic centralized in DropdownItem for easy reuse.
4
4
  */
5
5
  function createDropdownKeydownHandler(params) {
6
- const { activeIndex, onEnterSelect, onEscape, open, options, setActiveIndex, setListboxHasVisualFocus, setOpen, } = params;
6
+ const { activeIndex, onEnterSelect, onEscape, open, options, setActiveIndex, setKeyboardActiveIndex, setListboxHasVisualFocus, setOpen, } = params;
7
7
  return (e) => {
8
8
  if (options.length === 0)
9
9
  return;
@@ -15,13 +15,14 @@ function createDropdownKeydownHandler(params) {
15
15
  setOpen(true);
16
16
  setListboxHasVisualFocus(true);
17
17
  setActiveIndex(() => 0);
18
+ setKeyboardActiveIndex === null || setKeyboardActiveIndex === void 0 ? void 0 : setKeyboardActiveIndex(() => 0);
18
19
  return;
19
20
  }
20
21
  setListboxHasVisualFocus(true);
21
22
  setActiveIndex((prev) => {
22
- if (prev === null)
23
- return 0;
24
- return prev >= options.length - 1 ? 0 : prev + 1;
23
+ const next = prev === null ? 0 : prev >= options.length - 1 ? 0 : prev + 1;
24
+ setKeyboardActiveIndex === null || setKeyboardActiveIndex === void 0 ? void 0 : setKeyboardActiveIndex(() => next);
25
+ return next;
25
26
  });
26
27
  break;
27
28
  }
@@ -32,13 +33,18 @@ function createDropdownKeydownHandler(params) {
32
33
  setOpen(true);
33
34
  setListboxHasVisualFocus(true);
34
35
  setActiveIndex(() => options.length - 1);
36
+ setKeyboardActiveIndex === null || setKeyboardActiveIndex === void 0 ? void 0 : setKeyboardActiveIndex(() => options.length - 1);
35
37
  return;
36
38
  }
37
39
  setListboxHasVisualFocus(true);
38
40
  setActiveIndex((prev) => {
39
- if (prev === null)
40
- return options.length - 1;
41
- return prev <= 0 ? options.length - 1 : prev - 1;
41
+ const next = prev === null
42
+ ? options.length - 1
43
+ : prev <= 0
44
+ ? options.length - 1
45
+ : prev - 1;
46
+ setKeyboardActiveIndex === null || setKeyboardActiveIndex === void 0 ? void 0 : setKeyboardActiveIndex(() => next);
47
+ return next;
42
48
  });
43
49
  break;
44
50
  }
@@ -64,6 +70,7 @@ function createDropdownKeydownHandler(params) {
64
70
  case 'ArrowRight': {
65
71
  setListboxHasVisualFocus(false);
66
72
  setActiveIndex(() => null);
73
+ setKeyboardActiveIndex === null || setKeyboardActiveIndex === void 0 ? void 0 : setKeyboardActiveIndex(() => null);
67
74
  break;
68
75
  }
69
76
  }
@@ -13,7 +13,7 @@ export interface FilterProps extends Omit<NativeElementPropsWithoutKeyAndRef<'di
13
13
  */
14
14
  children: ReactElement<FormFieldProps> | ReactElement<FormFieldProps>[];
15
15
  /**
16
- * Layout control - Whether the field should automatically expand to fill the entire row (equivalent to span={12}).
16
+ * Layout control - Whether the field should automatically expand to fill the entire row (equivalent to span={6}).
17
17
  * @default false
18
18
  */
19
19
  grow?: boolean;
@@ -22,11 +22,34 @@ export interface FilterProps extends Omit<NativeElementPropsWithoutKeyAndRef<'di
22
22
  */
23
23
  minWidth?: string | number;
24
24
  /**
25
- * Layout control - Number of columns the field occupies in the Grid (1-12, Grid has 12 columns total).
25
+ * Layout control - Number of columns the field occupies in the Grid (1-6, Grid has 6 columns total).
26
26
  * This property is ignored when grow is true.
27
27
  * @default 2
28
28
  */
29
29
  span?: FilterSpan;
30
30
  }
31
+ /**
32
+ * 單一篩選條件元件,用於在 FilterLine 中定義欄位的佔位寬度。
33
+ *
34
+ * 使用 6 欄 Grid,`span` 決定欄位佔用幾欄(1–6);
35
+ * `grow` 設為 `true` 時欄位自動填滿整行。
36
+ * 從 FilterAreaContext 繼承 `size`,統一套用至內部的輸入元件。
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * import { Filter } from '@mezzanine-ui/react';
41
+ * import { FormField } from '@mezzanine-ui/react';
42
+ * import Input from '@mezzanine-ui/react/Input';
43
+ *
44
+ * <Filter span={2}>
45
+ * <FormField label="名稱" name="name" layout="horizontal">
46
+ * <Input placeholder="請輸入" />
47
+ * </FormField>
48
+ * </Filter>
49
+ * ```
50
+ *
51
+ * @see {@link FilterLine} 包含 Filter 的行容器
52
+ * @see {@link FilterArea} 管理整個篩選器的容器
53
+ */
31
54
  declare const Filter: import("react").ForwardRefExoticComponent<FilterProps & import("react").RefAttributes<HTMLDivElement>>;
32
55
  export default Filter;
@@ -5,6 +5,29 @@ import { filterAreaClasses, filterAreaPrefix } from '@mezzanine-ui/core/filter-a
5
5
  import FilterAreaContext from './FilterAreaContext.js';
6
6
  import cx from 'clsx';
7
7
 
8
+ /**
9
+ * 單一篩選條件元件,用於在 FilterLine 中定義欄位的佔位寬度。
10
+ *
11
+ * 使用 6 欄 Grid,`span` 決定欄位佔用幾欄(1–6);
12
+ * `grow` 設為 `true` 時欄位自動填滿整行。
13
+ * 從 FilterAreaContext 繼承 `size`,統一套用至內部的輸入元件。
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * import { Filter } from '@mezzanine-ui/react';
18
+ * import { FormField } from '@mezzanine-ui/react';
19
+ * import Input from '@mezzanine-ui/react/Input';
20
+ *
21
+ * <Filter span={2}>
22
+ * <FormField label="名稱" name="name" layout="horizontal">
23
+ * <Input placeholder="請輸入" />
24
+ * </FormField>
25
+ * </Filter>
26
+ * ```
27
+ *
28
+ * @see {@link FilterLine} 包含 Filter 的行容器
29
+ * @see {@link FilterArea} 管理整個篩選器的容器
30
+ */
8
31
  const Filter = forwardRef(function Filter(props, ref) {
9
32
  var _a;
10
33
  const { align = 'stretch', children, className, grow = false, minWidth, span = 2, ...rest } = props;
@@ -1,5 +1,5 @@
1
1
  import { ComponentPropsWithoutRef, ReactElement } from 'react';
2
- import { FilterAreaActionsAlign, FilterAreaSize } from '@mezzanine-ui/core/filter-area';
2
+ import { FilterAreaActionsAlign, FilterAreaRowAlign, FilterAreaSize } from '@mezzanine-ui/core/filter-area';
3
3
  import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
4
4
  import { FilterLineProps } from './FilterLine';
5
5
  export interface FilterAreaProps extends Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'children' | 'onSubmit' | 'onReset'> {
@@ -24,14 +24,14 @@ export interface FilterAreaProps extends Omit<NativeElementPropsWithoutKeyAndRef
24
24
  */
25
25
  onReset?: () => void;
26
26
  /**
27
- * Callback function triggered when the form is submitted.
27
+ * Callback function triggered when the submit button is clicked.
28
28
  * FilterArea itself does not manage form state; the parent component should collect
29
- * filter values and handle submission logic. If using react-hook-form, values will be
30
- * handled through FormProvider's handleSubmit.
29
+ * filter values and handle submission logic.
31
30
  */
32
31
  onSubmit?: () => void;
33
32
  /**
34
33
  * The text of the reset button.
34
+ * @default 'Reset'
35
35
  */
36
36
  resetText?: string;
37
37
  /**
@@ -41,6 +41,7 @@ export interface FilterAreaProps extends Omit<NativeElementPropsWithoutKeyAndRef
41
41
  size?: FilterAreaSize;
42
42
  /**
43
43
  * The text of the submit button.
44
+ * @default 'Search'
44
45
  */
45
46
  submitText?: string;
46
47
  /**
@@ -48,11 +49,49 @@ export interface FilterAreaProps extends Omit<NativeElementPropsWithoutKeyAndRef
48
49
  * @default 'button'
49
50
  */
50
51
  resetButtonType?: ComponentPropsWithoutRef<'button'>['type'];
52
+ /**
53
+ * The vertical alignment of the row (cross-axis align-items).
54
+ * @default 'center'
55
+ */
56
+ rowAlign?: FilterAreaRowAlign;
51
57
  /**
52
58
  * The type of the submit button.
53
59
  * @default 'button'
54
60
  */
55
61
  submitButtonType?: ComponentPropsWithoutRef<'button'>['type'];
56
62
  }
63
+ /**
64
+ * 篩選器容器元件,管理多個 FilterLine 的展示與收合。
65
+ *
66
+ * 預設僅顯示第一行(`FilterLine`),多行時自動出現展開/收合切換按鈕。
67
+ * 透過 `size` 統一控制內部所有表單欄位的尺寸;
68
+ * 透過 `actionsAlign` 調整「送出/重設」按鈕區塊的對齊;
69
+ * 透過 `isDirty` 控制重設按鈕的啟用狀態(`false` 時禁用)。
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * import { Filter, FilterArea, FilterLine } from '@mezzanine-ui/react';
74
+ * import { FormField } from '@mezzanine-ui/react';
75
+ * import Input from '@mezzanine-ui/react/Input';
76
+ *
77
+ * <FilterArea
78
+ * submitText="搜尋"
79
+ * resetText="重設"
80
+ * onSubmit={handleSubmit}
81
+ * onReset={handleReset}
82
+ * >
83
+ * <FilterLine>
84
+ * <Filter span={2}>
85
+ * <FormField label="名稱" name="name" layout="horizontal">
86
+ * <Input placeholder="請輸入" />
87
+ * </FormField>
88
+ * </Filter>
89
+ * </FilterLine>
90
+ * </FilterArea>
91
+ * ```
92
+ *
93
+ * @see {@link FilterLine} 用於組成 FilterArea 的單行條件列
94
+ * @see {@link Filter} 包裝單一篩選欄位的元件
95
+ */
57
96
  declare const FilterArea: import("react").ForwardRefExoticComponent<FilterAreaProps & import("react").RefAttributes<HTMLDivElement>>;
58
97
  export default FilterArea;
@@ -7,8 +7,41 @@ import Button from '../Button/Button.js';
7
7
  import FilterAreaContext from './FilterAreaContext.js';
8
8
  import cx from 'clsx';
9
9
 
10
+ /**
11
+ * 篩選器容器元件,管理多個 FilterLine 的展示與收合。
12
+ *
13
+ * 預設僅顯示第一行(`FilterLine`),多行時自動出現展開/收合切換按鈕。
14
+ * 透過 `size` 統一控制內部所有表單欄位的尺寸;
15
+ * 透過 `actionsAlign` 調整「送出/重設」按鈕區塊的對齊;
16
+ * 透過 `isDirty` 控制重設按鈕的啟用狀態(`false` 時禁用)。
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * import { Filter, FilterArea, FilterLine } from '@mezzanine-ui/react';
21
+ * import { FormField } from '@mezzanine-ui/react';
22
+ * import Input from '@mezzanine-ui/react/Input';
23
+ *
24
+ * <FilterArea
25
+ * submitText="搜尋"
26
+ * resetText="重設"
27
+ * onSubmit={handleSubmit}
28
+ * onReset={handleReset}
29
+ * >
30
+ * <FilterLine>
31
+ * <Filter span={2}>
32
+ * <FormField label="名稱" name="name" layout="horizontal">
33
+ * <Input placeholder="請輸入" />
34
+ * </FormField>
35
+ * </Filter>
36
+ * </FilterLine>
37
+ * </FilterArea>
38
+ * ```
39
+ *
40
+ * @see {@link FilterLine} 用於組成 FilterArea 的單行條件列
41
+ * @see {@link Filter} 包裝單一篩選欄位的元件
42
+ */
10
43
  const FilterArea = forwardRef(function FilterArea(props, ref) {
11
- const { actionsAlign = 'end', children, className, isDirty = true, onReset, onSubmit, resetText = 'Reset', size = 'main', submitText = 'Search', resetButtonType = 'button', submitButtonType = 'button', ...rest } = props;
44
+ const { actionsAlign = 'end', children, className, isDirty = true, onReset, onSubmit, resetText = 'Reset', rowAlign = 'center', size = 'main', submitText = 'Search', resetButtonType = 'button', submitButtonType = 'button', ...rest } = props;
12
45
  const filterLines = useMemo(() => Children.toArray(children), [children]);
13
46
  const contextValue = useMemo(() => ({ size }), [size]);
14
47
  const hasMultipleLines = filterLines.length > 1;
@@ -27,7 +60,7 @@ const FilterArea = forwardRef(function FilterArea(props, ref) {
27
60
  const firstLine = filterLines[0];
28
61
  return (jsx(FilterAreaContext.Provider, { value: contextValue, children: jsx("div", { ...rest, ref: ref, className: cx(filterAreaClasses.host, className, {
29
62
  [filterAreaClasses.size(size)]: size,
30
- }), children: expanded ? (jsxs(Fragment, { children: [filterLines, jsx("div", { className: filterAreaClasses.row, children: renderAction() })] })) : (jsx(Fragment, { children: firstLine && (jsxs("div", { className: filterAreaClasses.row, children: [firstLine, renderAction()] })) })) }) }));
63
+ }), children: expanded ? (jsxs(Fragment, { children: [filterLines, jsx("div", { className: cx(filterAreaClasses.row, filterAreaClasses.rowAlign(rowAlign)), children: renderAction() })] })) : (jsx(Fragment, { children: firstLine && (jsxs("div", { className: cx(filterAreaClasses.row, filterAreaClasses.rowAlign(rowAlign)), children: [firstLine, renderAction()] })) })) }) }));
31
64
  });
32
65
 
33
66
  export { FilterArea as default };