@lumx/react 4.9.0-alpha.2 → 4.9.0-alpha.3

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 (4) hide show
  1. package/index.d.ts +116 -23
  2. package/index.js +403 -143
  3. package/index.js.map +1 -1
  4. package/package.json +3 -3
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Kind as Kind$1, Size as Size$1, ColorPalette as ColorPalette$1, Emphasis as Emphasis$1, ColorVariant, VISUALLY_HIDDEN, Theme as Theme$1, AspectRatio as AspectRatio$1, DOCUMENT, IS_BROWSER as IS_BROWSER$1, WINDOW, DIALOG_TRANSITION_DURATION, Orientation as Orientation$1, Alignment, NOTIFICATION_TRANSITION_DURATION } from '@lumx/core/js/constants';
1
+ import { Kind as Kind$1, Size as Size$1, ColorPalette as ColorPalette$1, Emphasis as Emphasis$1, ColorVariant, VISUALLY_HIDDEN, Theme as Theme$1, AspectRatio as AspectRatio$1, DOCUMENT, IS_BROWSER as IS_BROWSER$1, WINDOW, DIALOG_TRANSITION_DURATION, Orientation as Orientation$1, Alignment as Alignment$1, NOTIFICATION_TRANSITION_DURATION } from '@lumx/core/js/constants';
2
2
  export * from '@lumx/core/js/constants';
3
3
  export * from '@lumx/core/js/types';
4
4
  import * as React from 'react';
@@ -892,6 +892,8 @@ const TOOLTIP_LONG_PRESS_DELAY = {
892
892
  /**
893
893
  * Alignments.
894
894
  */
895
+ const Alignment = {
896
+ left: 'left'};
895
897
  const Theme = {
896
898
  light: 'light',
897
899
  dark: 'dark'
@@ -946,7 +948,10 @@ const AspectRatio = {
946
948
  /** Ratio 3:2 */
947
949
  horizontal: 'horizontal',
948
950
  /** Ratio 1:1 */
949
- square: 'square'};
951
+ square: 'square',
952
+ /** Ratio constrained by the parent. */
953
+ free: 'free'
954
+ };
950
955
  /**
951
956
  * Semantic info about the purpose of the component
952
957
  */
@@ -3239,6 +3244,9 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
3239
3244
  /** Last notified isEmpty state, to avoid redundant `emptyChange` notifications. */
3240
3245
  let lastIsEmpty = true;
3241
3246
 
3247
+ /** Last notified input value, to re-fire `emptyChange` when the user keeps typing while empty. */
3248
+ let lastInputValue = '';
3249
+
3242
3250
  /** Event subscribers managed by the handle. */
3243
3251
  const subscribers = {
3244
3252
  open: new Set(),
@@ -3254,7 +3262,9 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
3254
3262
  }
3255
3263
 
3256
3264
  /**
3257
- * Notify all registered sections and fire `emptyChange` if the visible option count changed.
3265
+ * Notify all registered sections and fire `emptyChange` if the visible option count changed
3266
+ * or if the input value changed while the list is empty (so `emptyMessage` callbacks get
3267
+ * the updated query string).
3258
3268
  * Called whenever the set of visible options may have changed (option register/unregister, filter change).
3259
3269
  */
3260
3270
  function notifyVisibilityChange() {
@@ -3266,9 +3276,10 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
3266
3276
  if (!reg.lastFiltered) visibleCount += 1;
3267
3277
  }
3268
3278
  const isEmpty = visibleCount === 0;
3269
- if (isEmpty !== lastIsEmpty) {
3279
+ const inputValue = trigger?.value ?? '';
3280
+ if (isEmpty !== lastIsEmpty || isEmpty && inputValue !== lastInputValue) {
3270
3281
  lastIsEmpty = isEmpty;
3271
- const inputValue = trigger?.value ?? '';
3282
+ lastInputValue = inputValue;
3272
3283
  notify('emptyChange', {
3273
3284
  isEmpty,
3274
3285
  inputValue
@@ -3287,6 +3298,22 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
3287
3298
  /** Timer for debounced loading announcement. */
3288
3299
  let loadingTimer;
3289
3300
 
3301
+ /** Whether a loading announcement has been sent since the last open. */
3302
+ let announcementSent = false;
3303
+
3304
+ /** Start or restart the debounced loading announcement timer if conditions are met. */
3305
+ function startLoadingAnnouncementTimer() {
3306
+ clearTimeout(loadingTimer);
3307
+ if (skeletonCount > 0 && isOpenState) {
3308
+ loadingTimer = setTimeout(() => {
3309
+ if (skeletonCount > 0 && isOpenState) {
3310
+ announcementSent = true;
3311
+ notify('loadingAnnouncement', true);
3312
+ }
3313
+ }, LOADING_ANNOUNCEMENT_DELAY);
3314
+ }
3315
+ }
3316
+
3290
3317
  /**
3291
3318
  * Called when the skeleton count transitions between 0 and >0 (or vice versa).
3292
3319
  * Fires `loadingChange` immediately and manages the debounced `loadingAnnouncement`.
@@ -3294,15 +3321,14 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
3294
3321
  function onSkeletonCountChange() {
3295
3322
  const isLoading = skeletonCount > 0;
3296
3323
  notify('loadingChange', isLoading);
3297
- clearTimeout(loadingTimer);
3298
3324
  if (isLoading) {
3299
- loadingTimer = setTimeout(() => {
3300
- if (skeletonCount > 0) {
3301
- notify('loadingAnnouncement', true);
3302
- }
3303
- }, LOADING_ANNOUNCEMENT_DELAY);
3325
+ startLoadingAnnouncementTimer();
3304
3326
  } else {
3305
- notify('loadingAnnouncement', false);
3327
+ clearTimeout(loadingTimer);
3328
+ if (announcementSent) {
3329
+ announcementSent = false;
3330
+ notify('loadingAnnouncement', false);
3331
+ }
3306
3332
  }
3307
3333
  }
3308
3334
 
@@ -3489,11 +3515,33 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
3489
3515
  isOpenState = isOpen;
3490
3516
  if (!isOpen) {
3491
3517
  focusNav?.clear();
3518
+ // Reset announcement state so it retriggers on next open
3519
+ clearTimeout(loadingTimer);
3520
+ if (announcementSent) {
3521
+ announcementSent = false;
3522
+ notify('loadingAnnouncement', false);
3523
+ }
3524
+ } else if (skeletonCount > 0) {
3525
+ // Opening while already loading — start the announcement timer
3526
+ startLoadingAnnouncementTimer();
3492
3527
  }
3493
3528
 
3494
3529
  // Update aria-expanded on trigger
3495
3530
  trigger?.setAttribute('aria-expanded', String(isOpen));
3496
3531
  notify('open', isOpen);
3532
+
3533
+ // When opening, always notify the current empty state so that
3534
+ // subscribers (ComboboxState) get the correct initial value.
3535
+ // Without this, a list that starts empty never fires `emptyChange`
3536
+ // because `lastIsEmpty` is initialized to `true` and `notifyVisibilityChange`
3537
+ // only fires on *changes*.
3538
+ if (isOpen) {
3539
+ const inputValue = trigger?.value ?? '';
3540
+ notify('emptyChange', {
3541
+ isEmpty: lastIsEmpty,
3542
+ inputValue
3543
+ });
3544
+ }
3497
3545
  },
3498
3546
  select(option) {
3499
3547
  callbacks.onSelect({
@@ -3610,10 +3658,12 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
3610
3658
  listbox = null;
3611
3659
  filterValue = '';
3612
3660
  lastIsEmpty = true;
3661
+ lastInputValue = '';
3613
3662
  optionRegistrations.clear();
3614
3663
  sectionRegistrations.clear();
3615
3664
  skeletonCount = 0;
3616
3665
  clearTimeout(loadingTimer);
3666
+ announcementSent = false;
3617
3667
  // Clear all subscribers
3618
3668
  for (const set of Object.values(subscribers)) {
3619
3669
  set.clear();
@@ -8013,9 +8063,10 @@ const ComboboxOptionMoreInfo$1 = (props, {
8013
8063
  return /*#__PURE__*/jsxs(Fragment, {
8014
8064
  children: [/*#__PURE__*/jsx(IconButton, {
8015
8065
  ref: ref,
8016
- className: block$R([className]),
8017
8066
  icon: mdiInformationOutline,
8018
8067
  size: "s",
8068
+ ...buttonProps,
8069
+ className: block$R([className, buttonProps?.className]),
8019
8070
  emphasis: "low",
8020
8071
  onMouseEnter: onMouseEnter,
8021
8072
  onMouseLeave: onMouseLeave
@@ -8023,8 +8074,7 @@ const ComboboxOptionMoreInfo$1 = (props, {
8023
8074
  // Keyboard accessibility is handled via combobox keyboard highlighting.
8024
8075
  ,
8025
8076
  "aria-hidden": true,
8026
- label: "",
8027
- ...buttonProps
8077
+ label: ""
8028
8078
  }), /*#__PURE__*/jsx(Popover, {
8029
8079
  id: popoverId,
8030
8080
  className: element$G('popover'),
@@ -8034,6 +8084,7 @@ const ComboboxOptionMoreInfo$1 = (props, {
8034
8084
  closeOnEscape: true,
8035
8085
  closeOnClickAway: true,
8036
8086
  placement: "bottom-end",
8087
+ hasArrow: true,
8037
8088
  children: children
8038
8089
  })]
8039
8090
  });
@@ -8835,9 +8886,10 @@ const ComboboxOptionSkeleton$1 = props => {
8835
8886
  children,
8836
8887
  className,
8837
8888
  ref,
8889
+ count = 1,
8838
8890
  ...forwardedProps
8839
8891
  } = props;
8840
- return ListItem$1({
8892
+ const itemProps = {
8841
8893
  ref,
8842
8894
  size: 'tiny',
8843
8895
  role: 'none',
@@ -8852,6 +8904,13 @@ const ComboboxOptionSkeleton$1 = props => {
8852
8904
  theme: "light"
8853
8905
  })]
8854
8906
  })
8907
+ };
8908
+ return /*#__PURE__*/jsx(Fragment, {
8909
+ children: Array.from({
8910
+ length: count
8911
+ }, (_, i) => /*#__PURE__*/jsx(ListItem$1, {
8912
+ ...itemProps
8913
+ }, i))
8855
8914
  });
8856
8915
  };
8857
8916
 
@@ -8880,19 +8939,13 @@ const ComboboxOptionSkeleton$1 = props => {
8880
8939
  * @return React element(s).
8881
8940
  */
8882
8941
  const ComboboxOptionSkeleton = props => {
8883
- const {
8884
- count = 1,
8885
- ...itemProps
8886
- } = props;
8887
8942
  const {
8888
8943
  handle
8889
8944
  } = useComboboxContext();
8890
8945
  useEffect(() => handle?.registerSkeleton(), [handle]);
8891
- return Array.from({
8892
- length: count
8893
- }, (_, i) => /*#__PURE__*/jsx(ComboboxOptionSkeleton$1, {
8894
- ...itemProps
8895
- }, i));
8946
+ return /*#__PURE__*/jsx(ComboboxOptionSkeleton$1, {
8947
+ ...props
8948
+ });
8896
8949
  };
8897
8950
  ComboboxOptionSkeleton.displayName = COMPONENT_NAME$12;
8898
8951
  ComboboxOptionSkeleton.className = CLASSNAME$11;
@@ -9443,11 +9496,12 @@ const ListSection$1 = props => {
9443
9496
  ...forwardedProps
9444
9497
  } = props;
9445
9498
  const labelId = `${id}-label`;
9499
+ const hasHeader = !!label;
9446
9500
  return /*#__PURE__*/jsxs("li", {
9447
9501
  ref: ref,
9448
9502
  ...forwardedProps,
9449
9503
  className: classnames(className, block$O()),
9450
- children: [label && /*#__PURE__*/jsxs(Text, {
9504
+ children: [hasHeader && /*#__PURE__*/jsxs(Text, {
9451
9505
  as: "p",
9452
9506
  typography: "overline",
9453
9507
  className: element$D('title'),
@@ -9458,7 +9512,7 @@ const ListSection$1 = props => {
9458
9512
  }), /*#__PURE__*/jsx("ul", {
9459
9513
  ...itemsWrapperProps,
9460
9514
  className: element$D('items'),
9461
- "aria-labelledby": label ? labelId : undefined,
9515
+ "aria-labelledby": hasHeader ? labelId : undefined,
9462
9516
  children: children
9463
9517
  })]
9464
9518
  });
@@ -9619,6 +9673,7 @@ const ComboboxState$1 = (props, {
9619
9673
  loadingMessage,
9620
9674
  state
9621
9675
  } = props;
9676
+ const isOpen = state?.isOpen ?? true;
9622
9677
  const showError = !!errorMessage;
9623
9678
  const resolvedEmptyMessage = typeof emptyMessage === 'function' ? emptyMessage(state?.inputValue || '') : emptyMessage;
9624
9679
  // Suppress empty while loading (immediate flag prevents false "no results" before data arrives)
@@ -9632,6 +9687,12 @@ const ComboboxState$1 = (props, {
9632
9687
  hAlign: 'center',
9633
9688
  vAlign: 'center'
9634
9689
  };
9690
+
9691
+ // Gate message content behind isOpen so that content is *inserted* into the
9692
+ // aria-live region when the popover opens, triggering screen reader announcements.
9693
+ // Without this gate, content is already present (just hidden via display:none from
9694
+ // the popover's closeMode="hide") and revealing it doesn't trigger announcements.
9695
+ const renderContent = isOpen;
9635
9696
  return /*#__PURE__*/jsxs(GenericBlock, {
9636
9697
  className: classnames(!show && visuallyHidden(), block$N(), padding('regular')),
9637
9698
  orientation: "vertical",
@@ -9639,17 +9700,17 @@ const ComboboxState$1 = (props, {
9639
9700
  role: "status",
9640
9701
  "aria-live": "polite",
9641
9702
  "aria-atomic": true,
9642
- children: [showEmpty && /*#__PURE__*/jsx(Text, {
9703
+ children: [renderContent && showEmpty && /*#__PURE__*/jsx(Text, {
9643
9704
  as: "p",
9644
9705
  typography: "body1",
9645
9706
  color: "dark-L2",
9646
9707
  children: resolvedEmptyMessage
9647
- }), showLoading && /*#__PURE__*/jsx(Text, {
9708
+ }), renderContent && showLoading && /*#__PURE__*/jsx(Text, {
9648
9709
  as: "p",
9649
9710
  typography: "body1",
9650
9711
  color: "dark-L2",
9651
9712
  children: loadingMessage
9652
- }), !!errorMessage && /*#__PURE__*/jsxs(Fragment, {
9713
+ }), renderContent && !!errorMessage && /*#__PURE__*/jsxs(Fragment, {
9653
9714
  children: [/*#__PURE__*/jsx(Text, {
9654
9715
  as: "p",
9655
9716
  typography: "subtitle2",
@@ -9664,6 +9725,62 @@ const ComboboxState$1 = (props, {
9664
9725
  });
9665
9726
  };
9666
9727
 
9728
+ /**
9729
+ * Delay before inserting content into the aria-live region after the popover opens (ms).
9730
+ *
9731
+ * The popover uses `closeMode="hide"` (`display:none` when closed), so the live region
9732
+ * container is not in the accessibility tree until the popover becomes visible.
9733
+ * Screen readers only detect content *changes* in live regions that are already visible.
9734
+ * This delay ensures the popover's `display:none` is removed and the accessibility tree
9735
+ * is updated before content is inserted, so screen readers reliably announce it.
9736
+ */
9737
+ const OPEN_ANNOUNCEMENT_DELAY = 100;
9738
+
9739
+ /** Setters invoked by `subscribeComboboxState` when handle events fire. */
9740
+
9741
+ /**
9742
+ * Subscribe to the combobox handle events needed by `ComboboxState`.
9743
+ *
9744
+ * Manages three subscriptions:
9745
+ * - `loadingChange` → `setIsLoading` (+ synchronous initial read of `handle.isLoading`)
9746
+ * - `loadingAnnouncement` → `setShouldAnnounce`
9747
+ * - `open` → `setIsOpen` (deferred by {@link OPEN_ANNOUNCEMENT_DELAY}ms on open, immediate on close)
9748
+ *
9749
+ * @param handle The combobox handle to subscribe to.
9750
+ * @param setters Framework-specific state setters.
9751
+ * @returns A cleanup function that unsubscribes all events and clears timers.
9752
+ */
9753
+ function subscribeComboboxState(handle, setters) {
9754
+ const {
9755
+ setIsLoading,
9756
+ setShouldAnnounce,
9757
+ setIsOpen
9758
+ } = setters;
9759
+
9760
+ // Read current loading state synchronously
9761
+ setIsLoading(handle.isLoading);
9762
+ const unsubLoadingChange = handle.subscribe('loadingChange', setIsLoading);
9763
+ const unsubLoadingAnnouncement = handle.subscribe('loadingAnnouncement', setShouldAnnounce);
9764
+ let openTimer;
9765
+ const unsubOpen = handle.subscribe('open', open => {
9766
+ clearTimeout(openTimer);
9767
+ if (open) {
9768
+ // Delay content insertion so the popover is visible in the
9769
+ // accessibility tree before the live region content changes.
9770
+ openTimer = setTimeout(() => setIsOpen(true), OPEN_ANNOUNCEMENT_DELAY);
9771
+ } else {
9772
+ // Reset immediately on close
9773
+ setIsOpen(false);
9774
+ }
9775
+ });
9776
+ return () => {
9777
+ unsubLoadingChange();
9778
+ unsubLoadingAnnouncement();
9779
+ unsubOpen();
9780
+ clearTimeout(openTimer);
9781
+ };
9782
+ }
9783
+
9667
9784
  /**
9668
9785
  * Similar to lodash `partition` function but working with multiple predicates.
9669
9786
  *
@@ -9967,25 +10084,21 @@ const ComboboxState = props => {
9967
10084
  handle
9968
10085
  } = useComboboxContext();
9969
10086
  const emptyState = useComboboxEvent('emptyChange', undefined);
9970
-
9971
- // Track loading state with both initial read and event subscription
9972
- // (same pattern as ComboboxList for aria-busy)
9973
10087
  const [isLoading, setIsLoading] = useState(false);
9974
10088
  const [shouldAnnounce, setShouldAnnounce] = useState(false);
10089
+ const [isOpen, setIsOpen] = useState(false);
9975
10090
  useEffect(() => {
9976
10091
  if (!handle) return undefined;
9977
- // Read current state synchronously
9978
- setIsLoading(handle.isLoading);
9979
- const unsub1 = handle.subscribe('loadingChange', setIsLoading);
9980
- const unsub2 = handle.subscribe('loadingAnnouncement', setShouldAnnounce);
9981
- return () => {
9982
- unsub1();
9983
- unsub2();
9984
- };
10092
+ return subscribeComboboxState(handle, {
10093
+ setIsLoading,
10094
+ setShouldAnnounce,
10095
+ setIsOpen
10096
+ });
9985
10097
  }, [handle]);
9986
10098
  const state = {
9987
10099
  ...emptyState,
9988
- isLoading
10100
+ isLoading,
10101
+ isOpen
9989
10102
  };
9990
10103
 
9991
10104
  // Only pass loadingMessage to core after the 500ms debounce threshold
@@ -10006,16 +10119,27 @@ ComboboxState.className = CLASSNAME$X;
10006
10119
  * Combobox compound component namespace.
10007
10120
  */
10008
10121
  const Combobox = {
10122
+ /** Provides shared combobox context (handle, listbox ID, anchor ref) to all sub-components. */
10009
10123
  Provider: ComboboxProvider,
10124
+ /** Button trigger for select-only combobox mode with keyboard navigation and typeahead. */
10010
10125
  Button: ComboboxButton,
10126
+ /** Text input trigger for autocomplete combobox mode with optional toggle button and filtering. */
10011
10127
  Input: ComboboxInput,
10128
+ /** Listbox container that registers with the combobox handle and tracks loading state. */
10012
10129
  List: ComboboxList,
10130
+ /** Selectable option item with filtering and keyboard navigation support. */
10013
10131
  Option: ComboboxOption,
10132
+ /** Secondary action button within a grid-mode option row, rendered as an independent gridcell. */
10014
10133
  OptionAction: ComboboxOptionAction,
10134
+ /** Info button on an option that shows a popover on hover or keyboard highlight. */
10015
10135
  OptionMoreInfo: ComboboxOptionMoreInfo,
10136
+ /** Loading placeholder skeleton(s) that auto-register loading state with the combobox handle. */
10016
10137
  OptionSkeleton: ComboboxOptionSkeleton,
10138
+ /** Floating popover container that auto-binds to the combobox anchor and open/close state. */
10017
10139
  Popover: ComboboxPopover,
10140
+ /** Labelled group of options that auto-hides when all its child options are filtered out. */
10018
10141
  Section: ComboboxSection,
10142
+ /** Displays empty, error, and loading state messages for the combobox list. */
10019
10143
  State: ComboboxState,
10020
10144
  /** Visual separator between option groups (alias for ListDivider). Purely decorative — invisible to screen readers. */
10021
10145
  Divider: ListDivider,
@@ -11525,7 +11649,7 @@ const CLASSNAME$P = 'lumx-expansion-panel';
11525
11649
  const {
11526
11650
  block: block$G,
11527
11651
  element: element$x
11528
- } = classNames.bem(CLASSNAME$P);
11652
+ } = bem(CLASSNAME$P);
11529
11653
 
11530
11654
  /**
11531
11655
  * Component default props.
@@ -11533,9 +11657,6 @@ const {
11533
11657
  const DEFAULT_PROPS$L = {
11534
11658
  closeMode: 'unmount'
11535
11659
  };
11536
- const isDragHandle = isComponent(DragHandle);
11537
- const isHeader = isComponent('header');
11538
- const isFooter = isComponent('footer');
11539
11660
 
11540
11661
  /**
11541
11662
  * ExpansionPanel component.
@@ -11544,48 +11665,45 @@ const isFooter = isComponent('footer');
11544
11665
  * @param ref Component ref.
11545
11666
  * @return React element.
11546
11667
  */
11547
- const ExpansionPanel = forwardRef((props, ref) => {
11548
- const defaultTheme = useTheme() || Theme$1.light;
11668
+ const ExpansionPanel$1 = props => {
11549
11669
  const {
11550
11670
  className,
11551
- closeMode = DEFAULT_PROPS$L.closeMode,
11552
11671
  children: anyChildren,
11553
11672
  hasBackground,
11673
+ ref,
11554
11674
  hasHeaderDivider,
11555
11675
  isOpen,
11556
11676
  label,
11557
- onClose,
11558
- onOpen,
11559
- onToggleOpen,
11560
- theme = defaultTheme,
11677
+ handleClose,
11678
+ handleOpen,
11679
+ handleToggleOpen,
11680
+ theme,
11561
11681
  toggleButtonProps,
11682
+ headerProps,
11683
+ headerContent,
11684
+ dragHandle,
11685
+ wrapperRef,
11686
+ content,
11687
+ isChildrenVisible,
11688
+ IconButton,
11689
+ footer,
11690
+ closeMode,
11562
11691
  ...forwardedProps
11563
11692
  } = props;
11564
- const children = Children.toArray(anyChildren);
11565
-
11566
- // Partition children by types.
11567
- const [[dragHandle], [header], [footer], content] = partitionMulti(children, [isDragHandle, isHeader, isFooter]);
11568
-
11569
- // Either take the header in children or create one with the label.
11570
- const headerProps = /*#__PURE__*/React__default.isValidElement(header) ? header.props : {};
11571
- const headerContent = !isEmpty(headerProps.children) ? headerProps.children : /*#__PURE__*/jsx("span", {
11572
- className: element$x('label'),
11573
- children: label
11574
- });
11575
11693
  const toggleOpen = event => {
11576
11694
  const shouldOpen = !isOpen;
11577
- if (onOpen && shouldOpen) {
11578
- onOpen(event);
11695
+ if (handleOpen && shouldOpen) {
11696
+ handleOpen(event);
11579
11697
  }
11580
- if (onClose && !shouldOpen) {
11581
- onClose(event);
11698
+ if (handleClose && !shouldOpen) {
11699
+ handleClose(event);
11582
11700
  }
11583
- if (onToggleOpen) {
11584
- onToggleOpen(shouldOpen, event);
11701
+ if (handleToggleOpen) {
11702
+ handleToggleOpen(shouldOpen, event);
11585
11703
  }
11586
11704
  };
11587
- const color = theme === Theme$1.dark ? ColorPalette$1.light : ColorPalette$1.dark;
11588
- const rootClassName = classNames.join(className, block$G({
11705
+ const color = theme === Theme.dark ? ColorPalette.light : ColorPalette.dark;
11706
+ const rootClassName = classnames(className, block$G({
11589
11707
  'has-background': hasBackground,
11590
11708
  'has-header': Boolean(!isEmpty(headerProps.children)),
11591
11709
  'has-header-divider': hasHeaderDivider,
@@ -11594,35 +11712,6 @@ const ExpansionPanel = forwardRef((props, ref) => {
11594
11712
  'is-open': isOpen,
11595
11713
  [`theme-${theme}`]: Boolean(theme)
11596
11714
  }));
11597
- const wrapperRef = useRef(null);
11598
-
11599
- // Children stay visible while the open/close transition is running
11600
- const [isChildrenVisible, setChildrenVisible] = React__default.useState(isOpen);
11601
- const isOpenRef = React__default.useRef(isOpen);
11602
- React__default.useEffect(() => {
11603
- if (isOpen || closeMode === 'hide') {
11604
- setChildrenVisible(true);
11605
- } else if (!IS_BROWSER$1) {
11606
- // Outside a browser we can't wait for the transition
11607
- setChildrenVisible(false);
11608
- }
11609
- isOpenRef.current = isOpen;
11610
- }, [closeMode, isOpen]);
11611
-
11612
- // Change children's visibility on the transition end
11613
- React__default.useEffect(() => {
11614
- const {
11615
- current: wrapper
11616
- } = wrapperRef;
11617
- if (!IS_BROWSER$1 || !wrapper) {
11618
- return undefined;
11619
- }
11620
- const onTransitionEnd = () => {
11621
- setChildrenVisible(isOpenRef.current || closeMode === 'hide');
11622
- };
11623
- wrapper.addEventListener('transitionend', onTransitionEnd);
11624
- return () => wrapper.removeEventListener('transitionend', onTransitionEnd);
11625
- }, [closeMode]);
11626
11715
  return /*#__PURE__*/jsxs("section", {
11627
11716
  ref: ref,
11628
11717
  ...forwardedProps,
@@ -11642,7 +11731,7 @@ const ExpansionPanel = forwardRef((props, ref) => {
11642
11731
  children: /*#__PURE__*/jsx(IconButton, {
11643
11732
  ...toggleButtonProps,
11644
11733
  color: color,
11645
- emphasis: Emphasis$1.low,
11734
+ emphasis: Emphasis.low,
11646
11735
  icon: isOpen ? mdiChevronUp : mdiChevronDown,
11647
11736
  "aria-expanded": isOpen || 'false'
11648
11737
  })
@@ -11662,6 +11751,92 @@ const ExpansionPanel = forwardRef((props, ref) => {
11662
11751
  })
11663
11752
  })]
11664
11753
  });
11754
+ };
11755
+
11756
+ const isDragHandle = isComponent(DragHandle);
11757
+ const isHeader = isComponent('header');
11758
+ const isFooter = isComponent('footer');
11759
+
11760
+ /**
11761
+ * ExpansionPanel component.
11762
+ *
11763
+ * @param props Component props.
11764
+ * @param ref Component ref.
11765
+ * @return React element.
11766
+ */
11767
+ const ExpansionPanel = forwardRef((props, ref) => {
11768
+ const defaultTheme = useTheme() || Theme$1.light;
11769
+ const {
11770
+ closeMode = DEFAULT_PROPS$L.closeMode,
11771
+ children: anyChildren,
11772
+ isOpen,
11773
+ label,
11774
+ onClose,
11775
+ onOpen,
11776
+ onToggleOpen,
11777
+ theme = defaultTheme,
11778
+ ...forwardedProps
11779
+ } = props;
11780
+ const children = Children.toArray(anyChildren);
11781
+
11782
+ // Partition children by types.
11783
+ const [[dragHandle], [header], [footer], content] = partitionMulti(children, [isDragHandle, isHeader, isFooter]);
11784
+
11785
+ // Either take the header in children or create one with the label.
11786
+ const headerProps = /*#__PURE__*/React__default.isValidElement(header) ? header.props : {};
11787
+ const headerContent = !isEmpty(headerProps.children) ? headerProps.children : /*#__PURE__*/jsx("span", {
11788
+ className: element$x('label'),
11789
+ children: label
11790
+ });
11791
+ const wrapperRef = useRef(null);
11792
+
11793
+ // Children stay visible while the open/close transition is running
11794
+ const [isChildrenVisible, setChildrenVisible] = React__default.useState(isOpen);
11795
+ const isOpenRef = React__default.useRef(isOpen);
11796
+ React__default.useEffect(() => {
11797
+ if (isOpen || closeMode === 'hide') {
11798
+ setChildrenVisible(true);
11799
+ } else if (!IS_BROWSER$1) {
11800
+ // Outside a browser we can't wait for the transition
11801
+ setChildrenVisible(false);
11802
+ }
11803
+ isOpenRef.current = isOpen;
11804
+ }, [closeMode, isOpen]);
11805
+
11806
+ // Change children's visibility on the transition end
11807
+ React__default.useEffect(() => {
11808
+ const {
11809
+ current: wrapper
11810
+ } = wrapperRef;
11811
+ if (!IS_BROWSER$1 || !wrapper) {
11812
+ return undefined;
11813
+ }
11814
+ const onTransitionEnd = () => {
11815
+ setChildrenVisible(isOpenRef.current || closeMode === 'hide');
11816
+ };
11817
+ wrapper.addEventListener('transitionend', onTransitionEnd);
11818
+ return () => wrapper.removeEventListener('transitionend', onTransitionEnd);
11819
+ }, [closeMode]);
11820
+ return ExpansionPanel$1({
11821
+ content,
11822
+ dragHandle,
11823
+ footer,
11824
+ headerContent,
11825
+ ref,
11826
+ headerProps,
11827
+ wrapperRef,
11828
+ IconButton,
11829
+ isOpen,
11830
+ handleClose: onClose,
11831
+ handleToggleOpen: onToggleOpen,
11832
+ handleOpen: onOpen,
11833
+ theme,
11834
+ isChildrenVisible,
11835
+ children,
11836
+ closeMode,
11837
+ label,
11838
+ ...forwardedProps
11839
+ });
11665
11840
  });
11666
11841
  ExpansionPanel.displayName = COMPONENT_NAME$O;
11667
11842
  ExpansionPanel.className = CLASSNAME$P;
@@ -12239,7 +12414,7 @@ const {
12239
12414
  */
12240
12415
  const DEFAULT_PROPS$F = {
12241
12416
  captionPosition: ImageBlockCaptionPosition.below,
12242
- align: Alignment.left
12417
+ align: Alignment$1.left
12243
12418
  };
12244
12419
 
12245
12420
  /**
@@ -13653,7 +13828,7 @@ const CLASSNAME$B = 'lumx-mosaic';
13653
13828
  const {
13654
13829
  block: block$v,
13655
13830
  element: element$n
13656
- } = classNames.bem(CLASSNAME$B);
13831
+ } = bem(CLASSNAME$B);
13657
13832
 
13658
13833
  /**
13659
13834
  * Component default props.
@@ -13667,26 +13842,27 @@ const DEFAULT_PROPS$C = {};
13667
13842
  * @param ref Component ref.
13668
13843
  * @return React element.
13669
13844
  */
13670
- const Mosaic = forwardRef((props, ref) => {
13671
- const defaultTheme = useTheme() || Theme$1.light;
13845
+ const Mosaic$1 = props => {
13672
13846
  const {
13673
13847
  className,
13674
- theme = defaultTheme,
13848
+ theme,
13675
13849
  thumbnails,
13676
- onImageClick,
13850
+ handleClick,
13851
+ Thumbnail,
13852
+ ref,
13677
13853
  ...forwardedProps
13678
13854
  } = props;
13679
- const handleImageClick = useMemo(() => {
13680
- if (!onImageClick) return undefined;
13855
+ const onImageClick = () => {
13856
+ if (!handleClick) return undefined;
13681
13857
  return (index, onClick) => event => {
13682
13858
  onClick?.(event);
13683
- onImageClick?.(index);
13859
+ handleClick?.(index);
13684
13860
  };
13685
- }, [onImageClick]);
13861
+ };
13686
13862
  return /*#__PURE__*/jsx("div", {
13687
13863
  ref: ref,
13688
13864
  ...forwardedProps,
13689
- className: classNames.join(className, block$v({
13865
+ className: classnames(className, block$v({
13690
13866
  [`theme-${theme}`]: Boolean(theme),
13691
13867
  'has-1-thumbnail': thumbnails?.length === 1,
13692
13868
  'has-2-thumbnails': thumbnails?.length === 2,
@@ -13709,9 +13885,9 @@ const Mosaic = forwardRef((props, ref) => {
13709
13885
  align: align || Alignment.left,
13710
13886
  image: image,
13711
13887
  theme: theme,
13712
- aspectRatio: AspectRatio$1.free,
13888
+ aspectRatio: AspectRatio.free,
13713
13889
  fillHeight: true,
13714
- onClick: handleImageClick?.(index, onClick) || onClick
13890
+ onClick: onImageClick()?.(index, onClick) || onClick
13715
13891
  }), thumbnails.length > 4 && index === 3 && /*#__PURE__*/jsx("div", {
13716
13892
  className: element$n('overlay'),
13717
13893
  children: /*#__PURE__*/jsxs("span", {
@@ -13722,6 +13898,33 @@ const Mosaic = forwardRef((props, ref) => {
13722
13898
  })
13723
13899
  })
13724
13900
  });
13901
+ };
13902
+
13903
+ /**
13904
+ * Defines the props of the component.
13905
+ */
13906
+
13907
+ /**
13908
+ * Mosaic component.
13909
+ *
13910
+ * @param props Component props.
13911
+ * @param ref Component ref.
13912
+ * @return React element.
13913
+ */
13914
+ const Mosaic = forwardRef((props, ref) => {
13915
+ const defaultTheme = useTheme() || Theme$1.light;
13916
+ const {
13917
+ theme = defaultTheme,
13918
+ onImageClick,
13919
+ ...forwardedProps
13920
+ } = props;
13921
+ return Mosaic$1({
13922
+ ref,
13923
+ theme,
13924
+ Thumbnail,
13925
+ handleClick: onImageClick,
13926
+ ...forwardedProps
13927
+ });
13725
13928
  });
13726
13929
  Mosaic.displayName = COMPONENT_NAME$A;
13727
13930
  Mosaic.className = CLASSNAME$B;
@@ -14521,7 +14724,6 @@ const reducer = (state, action) => {
14521
14724
  if (state.activeTabIndex === action.payload) {
14522
14725
  return state;
14523
14726
  }
14524
- // Change active tab index.
14525
14727
  return {
14526
14728
  ...state,
14527
14729
  activeTabIndex: action.payload
@@ -14533,7 +14735,6 @@ const reducer = (state, action) => {
14533
14735
  type,
14534
14736
  id
14535
14737
  } = action.payload;
14536
- // Append tab/tabPanel id in state.
14537
14738
  return {
14538
14739
  ...state,
14539
14740
  ids: {
@@ -14550,7 +14751,6 @@ const reducer = (state, action) => {
14550
14751
  } = action.payload;
14551
14752
  const index = state.ids[type].indexOf(id);
14552
14753
  if (index === -1) return state;
14553
- // Remove tab & tab panel at index.
14554
14754
  const tabIds = [...state.ids.tab];
14555
14755
  tabIds.splice(index, 1);
14556
14756
  const tabPanelIds = [...state.ids.tabPanel];
@@ -14567,7 +14767,9 @@ const reducer = (state, action) => {
14567
14767
  return state;
14568
14768
  }
14569
14769
  };
14770
+
14570
14771
  const TabProviderContext = /*#__PURE__*/createContext(null);
14772
+
14571
14773
  /* eslint-disable react-hooks/rules-of-hooks */
14572
14774
  const useTabProviderContext = (type, originalId) => {
14573
14775
  const context = useContext(TabProviderContext);
@@ -17380,7 +17582,7 @@ const Switch$1 = props => {
17380
17582
  * Component default props.
17381
17583
  */
17382
17584
  const DEFAULT_PROPS$c = {
17383
- position: Alignment.left
17585
+ position: Alignment$1.left
17384
17586
  };
17385
17587
 
17386
17588
  /**
@@ -17939,7 +18141,7 @@ const TABS_CLASSNAME = `lumx-tabs`;
17939
18141
  const {
17940
18142
  block: block$6,
17941
18143
  element: element$4
17942
- } = classNames.bem(TABS_CLASSNAME);
18144
+ } = bem(TABS_CLASSNAME);
17943
18145
  let TabListLayout = /*#__PURE__*/function (TabListLayout) {
17944
18146
  TabListLayout["clustered"] = "clustered";
17945
18147
  TabListLayout["fixed"] = "fixed";
@@ -17972,26 +18174,21 @@ const DEFAULT_PROPS$6 = {
17972
18174
  * @param ref Component ref.
17973
18175
  * @return React element.
17974
18176
  */
17975
- const TabList = forwardRef((props, ref) => {
17976
- const defaultTheme = useTheme() || Theme$1.light;
18177
+ const TabList$1 = props => {
17977
18178
  const {
17978
18179
  'aria-label': ariaLabel,
17979
18180
  children,
17980
18181
  className,
17981
18182
  layout = DEFAULT_PROPS$6.layout,
17982
18183
  position = DEFAULT_PROPS$6.position,
17983
- theme = defaultTheme,
18184
+ theme,
18185
+ ref,
17984
18186
  ...forwardedProps
17985
18187
  } = props;
17986
- const tabListRef = React__default.useRef(null);
17987
- useRovingTabIndexContainer({
17988
- containerRef: tabListRef,
17989
- itemSelector: '[role="tab"]'
17990
- });
17991
18188
  return /*#__PURE__*/jsx("div", {
17992
- ref: mergeRefs(ref, tabListRef),
18189
+ ref: ref,
17993
18190
  ...forwardedProps,
17994
- className: classNames.join(className, block$6({
18191
+ className: classnames(className, block$6({
17995
18192
  [`layout-${layout}`]: Boolean(layout),
17996
18193
  [`position-${position}`]: Boolean(position),
17997
18194
  [`theme-${theme}`]: Boolean(theme)
@@ -18003,6 +18200,33 @@ const TabList = forwardRef((props, ref) => {
18003
18200
  children: children
18004
18201
  })
18005
18202
  });
18203
+ };
18204
+
18205
+ /**
18206
+ * TabList component.
18207
+ *
18208
+ * Implements WAI-ARIA `tablist` role {@see https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-1/tabs.html#rps_label}
18209
+ *
18210
+ * @param props Component props.
18211
+ * @param ref Component ref.
18212
+ * @return React element.
18213
+ */
18214
+ const TabList = forwardRef((props, ref) => {
18215
+ const defaultTheme = useTheme() || Theme$1.light;
18216
+ const {
18217
+ theme = defaultTheme,
18218
+ ...forwardedProps
18219
+ } = props;
18220
+ const tabListRef = React__default.useRef(null);
18221
+ useRovingTabIndexContainer({
18222
+ containerRef: tabListRef,
18223
+ itemSelector: '[role="tab"]'
18224
+ });
18225
+ return TabList$1({
18226
+ theme,
18227
+ ref: mergeRefs(ref, tabListRef),
18228
+ ...forwardedProps
18229
+ });
18006
18230
  });
18007
18231
  TabList.displayName = COMPONENT_NAME$5;
18008
18232
  TabList.className = TABS_CLASSNAME;
@@ -18041,12 +18265,15 @@ const Tab$1 = props => {
18041
18265
  icon,
18042
18266
  iconProps = {},
18043
18267
  isAnyDisabled,
18268
+ isDisabled,
18044
18269
  id,
18045
18270
  isActive,
18046
18271
  label,
18047
18272
  handleFocus,
18048
18273
  handleKeyPress,
18049
18274
  tabIndex = -1,
18275
+ tabIndexProp = 'tabIndex',
18276
+ keyPressProp = 'onKeyPress',
18050
18277
  changeToTab,
18051
18278
  tabPanelId,
18052
18279
  shouldActivateOnFocus,
@@ -18085,10 +18312,10 @@ const Tab$1 = props => {
18085
18312
  'is-disabled': isAnyDisabled
18086
18313
  })),
18087
18314
  onClick: changeToCurrentTab,
18088
- onKeyPress: onKeyPress,
18315
+ [keyPressProp]: onKeyPress,
18089
18316
  onFocus: onFocus,
18090
18317
  role: "tab",
18091
- tabIndex: isActive ? 0 : tabIndex,
18318
+ [tabIndexProp]: isActive ? 0 : tabIndex,
18092
18319
  "aria-disabled": isAnyDisabled,
18093
18320
  "aria-selected": isActive,
18094
18321
  "aria-controls": tabPanelId,
@@ -18159,7 +18386,7 @@ const COMPONENT_NAME$3 = 'TabPanel';
18159
18386
  const CLASSNAME$4 = `lumx-tab-panel`;
18160
18387
  const {
18161
18388
  block: block$4
18162
- } = classNames.bem(CLASSNAME$4);
18389
+ } = bem(CLASSNAME$4);
18163
18390
 
18164
18391
  /**
18165
18392
  * Component default props.
@@ -18175,27 +18402,60 @@ const DEFAULT_PROPS$4 = {};
18175
18402
  * @param ref Component ref.
18176
18403
  * @return React element.
18177
18404
  */
18178
- const TabPanel = forwardRef((props, ref) => {
18405
+ const TabPanel$1 = props => {
18179
18406
  const {
18180
18407
  children,
18181
- id,
18182
18408
  className,
18183
- isActive: propIsActive,
18409
+ isActive,
18410
+ id,
18411
+ tabId,
18412
+ isLazy,
18413
+ tabIndexProp = 'tabIndex',
18414
+ ref,
18184
18415
  ...forwardedProps
18185
18416
  } = props;
18186
- const state = useTabProviderContext('tabPanel', id);
18187
- const isActive = propIsActive || state?.isActive;
18188
18417
  return /*#__PURE__*/jsx("div", {
18189
18418
  ref: ref,
18190
18419
  ...forwardedProps,
18191
- id: state?.tabPanelId,
18192
- className: classNames.join(className, block$4({
18420
+ id: id,
18421
+ className: classnames(className, block$4({
18193
18422
  'is-active': isActive
18194
18423
  })),
18195
18424
  role: "tabpanel",
18196
- tabIndex: isActive ? 0 : -1,
18197
- "aria-labelledby": state?.tabId,
18198
- children: (!state?.isLazy || isActive) && children
18425
+ [tabIndexProp]: isActive ? 0 : -1,
18426
+ "aria-labelledby": tabId,
18427
+ children: (!isLazy || isActive) && children
18428
+ });
18429
+ };
18430
+
18431
+ /**
18432
+ * Defines the props of the component.
18433
+ */
18434
+
18435
+ /**
18436
+ * TabPanel component.
18437
+ *
18438
+ * Implements WAI-ARIA `tabpanel` role {@see https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-1/tabs.html#rps_label}
18439
+ *
18440
+ * @param props Component props.
18441
+ * @param ref Component ref.
18442
+ * @return React element.
18443
+ */
18444
+ const TabPanel = forwardRef((props, ref) => {
18445
+ const {
18446
+ id,
18447
+ isActive: propIsActive,
18448
+ ...forwardedProps
18449
+ } = props;
18450
+ const state = useTabProviderContext('tabPanel', id);
18451
+ const isActive = propIsActive || state?.isActive;
18452
+ return TabPanel$1({
18453
+ ref,
18454
+ isActive,
18455
+ id: state?.tabPanelId,
18456
+ isLazy: state?.isLazy,
18457
+ tabId: state?.tabId,
18458
+ ...forwardedProps
18199
18459
  });
18200
18460
  });
18201
18461
  TabPanel.displayName = COMPONENT_NAME$3;