@lumx/react 4.12.1-next.0 → 4.12.1-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +185 -93
- package/index.js.map +1 -1
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ColorVariant as ColorVariant$1, Size as Size$1, VISUALLY_HIDDEN, Theme as Theme$1, AspectRatio as AspectRatio$1, DOCUMENT, IS_BROWSER as IS_BROWSER$1, Emphasis as Emphasis$1, WINDOW, DIALOG_TRANSITION_DURATION, Orientation as Orientation$1, NOTIFICATION_TRANSITION_DURATION, Kind as Kind$1, Alignment as Alignment$1, ColorPalette as ColorPalette$1 } from '@lumx/core/js/constants';
|
|
1
|
+
import { ColorVariant as ColorVariant$1, Size as Size$1, VISUALLY_HIDDEN, Theme as Theme$1, AspectRatio as AspectRatio$1, DOCUMENT as DOCUMENT$1, IS_BROWSER as IS_BROWSER$1, Emphasis as Emphasis$1, WINDOW, DIALOG_TRANSITION_DURATION, Orientation as Orientation$1, NOTIFICATION_TRANSITION_DURATION, Kind as Kind$1, Alignment as Alignment$1, ColorPalette as ColorPalette$1 } 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';
|
|
@@ -204,6 +204,11 @@ const ColorVariant = {
|
|
|
204
204
|
*/
|
|
205
205
|
const IS_BROWSER = typeof window !== 'undefined' && !window.navigator.userAgent.includes('jsdom');
|
|
206
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Optional global `document` instance (not defined when running SSR).
|
|
209
|
+
*/
|
|
210
|
+
const DOCUMENT = typeof document !== 'undefined' ? document : undefined;
|
|
211
|
+
|
|
207
212
|
function getDefaultExportFromCjs (x) {
|
|
208
213
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
209
214
|
}
|
|
@@ -2519,23 +2524,29 @@ const isChipDisabled = chip => chip?.getAttribute('aria-disabled') === 'true';
|
|
|
2519
2524
|
*/
|
|
2520
2525
|
|
|
2521
2526
|
/**
|
|
2522
|
-
*
|
|
2523
|
-
*
|
|
2527
|
+
* Find the nearest enabled chip in the given direction relative to `anchor`.
|
|
2528
|
+
*
|
|
2529
|
+
* - When `anchor` is provided, walks one step in `direction` from it.
|
|
2530
|
+
* - When `anchor` is omitted, returns the first enabled chip from the matching
|
|
2531
|
+
* end of the container (last chip for 'previous', first for 'next').
|
|
2524
2532
|
*/
|
|
2525
|
-
function
|
|
2533
|
+
function findSiblingChip(container, direction, anchor) {
|
|
2526
2534
|
const walker = createSelectorTreeWalker(container, ENABLED_CHIP_SELECTOR);
|
|
2527
|
-
if (
|
|
2528
|
-
|
|
2535
|
+
if (anchor) {
|
|
2536
|
+
walker.currentNode = anchor;
|
|
2537
|
+
const node = direction === 'next' ? walker.nextNode() : walker.previousNode();
|
|
2538
|
+
return node || undefined;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
// No anchor: walk from the matching end of the container.
|
|
2542
|
+
if (direction === 'previous') {
|
|
2529
2543
|
let last;
|
|
2530
2544
|
while (walker.nextNode()) {
|
|
2531
2545
|
last = walker.currentNode;
|
|
2532
2546
|
}
|
|
2533
2547
|
return last;
|
|
2534
2548
|
}
|
|
2535
|
-
|
|
2536
|
-
// Position the walker at the current chip and walk backward.
|
|
2537
|
-
walker.currentNode = currentChip;
|
|
2538
|
-
return walker.previousNode() || undefined;
|
|
2549
|
+
return walker.nextNode() || undefined;
|
|
2539
2550
|
}
|
|
2540
2551
|
|
|
2541
2552
|
/** Remove an option by its id and call onChange. */
|
|
@@ -2574,9 +2585,6 @@ function setupSelectionChipGroupEvents(options) {
|
|
|
2574
2585
|
};
|
|
2575
2586
|
|
|
2576
2587
|
// Delegated keydown handler on the chip group container.
|
|
2577
|
-
// Enter/Space trigger removal (like click).
|
|
2578
|
-
// Backspace also removes but explicitly moves focus to previous chip (overriding the
|
|
2579
|
-
// roving tabindex MutationObserver which defaults to moving focus forward).
|
|
2580
2588
|
const handleKeyDown = evt => {
|
|
2581
2589
|
const chip = getChip(evt.target);
|
|
2582
2590
|
const optionId = chip?.dataset.optionId;
|
|
@@ -2584,14 +2592,20 @@ function setupSelectionChipGroupEvents(options) {
|
|
|
2584
2592
|
if (optionId == null || !activatingKey || isChipDisabled(chip)) {
|
|
2585
2593
|
return;
|
|
2586
2594
|
}
|
|
2595
|
+
|
|
2596
|
+
// Compute focus fallback target before removing the chip.
|
|
2597
|
+
let focusTarget;
|
|
2587
2598
|
if (evt.key === 'Backspace') {
|
|
2588
|
-
//
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2599
|
+
// Custom behavior (not WAI-ARIA recommendation) => focus the previous chip, fallback on input (no more chips)
|
|
2600
|
+
focusTarget = findSiblingChip(container, 'previous', chip) || options.getInput?.() || undefined;
|
|
2601
|
+
} else {
|
|
2602
|
+
// WAI-ARIA recommendation when removing an option in a listbox => focus the next chip, fallback on previous chip
|
|
2603
|
+
// (bonus: we fallback on input when there is no more chips)
|
|
2604
|
+
focusTarget = findSiblingChip(container, 'next', chip) || findSiblingChip(container, 'previous', chip) || options.getInput?.() || undefined;
|
|
2605
|
+
}
|
|
2606
|
+
if (focusTarget) {
|
|
2607
|
+
focusTarget.focus();
|
|
2608
|
+
focusTarget.setAttribute('tabindex', '0');
|
|
2595
2609
|
}
|
|
2596
2610
|
evt.preventDefault();
|
|
2597
2611
|
removeOption(options, optionId);
|
|
@@ -2609,7 +2623,7 @@ function setupSelectionChipGroupEvents(options) {
|
|
|
2609
2623
|
if (!backspacePressed || !cursorAtStart) return;
|
|
2610
2624
|
evt.stopPropagation();
|
|
2611
2625
|
evt.preventDefault();
|
|
2612
|
-
const lastChip =
|
|
2626
|
+
const lastChip = findSiblingChip(container, 'previous');
|
|
2613
2627
|
lastChip?.focus();
|
|
2614
2628
|
};
|
|
2615
2629
|
input.addEventListener('keydown', inputHandler);
|
|
@@ -5051,7 +5065,7 @@ const LISTENERS = makeListenerTowerContext();
|
|
|
5051
5065
|
*/
|
|
5052
5066
|
function useCallbackOnEscape(callback, closeOnEscape = true) {
|
|
5053
5067
|
useEffect(() => {
|
|
5054
|
-
const rootElement = DOCUMENT?.body;
|
|
5068
|
+
const rootElement = DOCUMENT$1?.body;
|
|
5055
5069
|
if (!closeOnEscape || !callback || !rootElement) {
|
|
5056
5070
|
return undefined;
|
|
5057
5071
|
}
|
|
@@ -5266,7 +5280,7 @@ const Tooltip = forwardRef((props, ref) => {
|
|
|
5266
5280
|
...forwardedProps
|
|
5267
5281
|
} = props;
|
|
5268
5282
|
// Disable in SSR.
|
|
5269
|
-
if (!DOCUMENT) {
|
|
5283
|
+
if (!DOCUMENT$1) {
|
|
5270
5284
|
return /*#__PURE__*/jsx(Fragment, {
|
|
5271
5285
|
children: children
|
|
5272
5286
|
});
|
|
@@ -8804,8 +8818,14 @@ const ComboboxOptionMoreInfo$1 = (props, {
|
|
|
8804
8818
|
/** CSS selector listing all tabbable elements. */
|
|
8805
8819
|
const TABBABLE_ELEMENTS_SELECTOR = 'a[href], button, textarea, input:not([type="hidden"]):not([hidden]), [tabindex]';
|
|
8806
8820
|
|
|
8807
|
-
/**
|
|
8808
|
-
|
|
8821
|
+
/**
|
|
8822
|
+
* CSS selector matching elements that should be excluded from focus traversal.
|
|
8823
|
+
*
|
|
8824
|
+
* Note: `aria-disabled` is intentionally NOT in this list — per ARIA semantics, an `aria-disabled` element
|
|
8825
|
+
* remains focusable (and discoverable by assistive tech). To remove an element from the tab order, use
|
|
8826
|
+
* `tabindex="-1"` instead.
|
|
8827
|
+
*/
|
|
8828
|
+
const DISABLED_SELECTOR = '[hidden], [tabindex="-1"], [disabled]:not([disabled="false"])';
|
|
8809
8829
|
|
|
8810
8830
|
const isNotDisabled = element => !element.matches(DISABLED_SELECTOR);
|
|
8811
8831
|
function getFocusableElements(element) {
|
|
@@ -8834,86 +8854,158 @@ function getFirstAndLastFocusable(parentElement) {
|
|
|
8834
8854
|
return {};
|
|
8835
8855
|
}
|
|
8836
8856
|
|
|
8857
|
+
/**
|
|
8858
|
+
* Shared listener tower for focus traps.
|
|
8859
|
+
*
|
|
8860
|
+
* When multiple traps are activated, only the last registered one is active. When it tears down, the previously
|
|
8861
|
+
* registered trap is re-enabled.
|
|
8862
|
+
*/
|
|
8837
8863
|
const FOCUS_TRAPS = makeListenerTowerContext();
|
|
8838
|
-
|
|
8839
8864
|
/**
|
|
8840
8865
|
* Trap 'Tab' focus switch inside the `focusZoneElement`.
|
|
8841
8866
|
*
|
|
8842
|
-
*
|
|
8843
|
-
*
|
|
8867
|
+
* Setup behavior:
|
|
8868
|
+
* 1. Focus `focusElement` if provided and contained in the zone.
|
|
8869
|
+
* 2. Otherwise focus the first focusable descendant.
|
|
8870
|
+
* 3. Otherwise focus the zone element itself (falling back to setting `tabindex="-1"` if needed) so that
|
|
8871
|
+
* keyboard users (especially screen reader users) land inside the trapped region (e.g. an empty dialog).
|
|
8844
8872
|
*
|
|
8845
|
-
*
|
|
8846
|
-
*
|
|
8847
|
-
*
|
|
8873
|
+
* Tab key behavior:
|
|
8874
|
+
* - With at least one focusable descendant: focus cycles between the first and last focusable in the zone.
|
|
8875
|
+
* - With no focusable descendant: Tab is swallowed and focus is restored to the zone element itself.
|
|
8876
|
+
*
|
|
8877
|
+
* Multiple traps stack — only the latest one is active; previous traps re-enable when the latest is torn down.
|
|
8878
|
+
*
|
|
8879
|
+
* @param options Trap configuration.
|
|
8880
|
+
* @param signal AbortSignal used to tear down the trap.
|
|
8848
8881
|
*/
|
|
8849
|
-
function
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8882
|
+
function setupFocusTrap(options, signal) {
|
|
8883
|
+
const {
|
|
8884
|
+
focusZoneElement,
|
|
8885
|
+
focusElement
|
|
8886
|
+
} = options;
|
|
8887
|
+
|
|
8888
|
+
// Body element can be undefined in SSR context.
|
|
8889
|
+
const rootElement = DOCUMENT?.body;
|
|
8890
|
+
if (!rootElement || !focusZoneElement || signal.aborted) {
|
|
8891
|
+
return;
|
|
8892
|
+
}
|
|
8893
|
+
|
|
8894
|
+
// Use the shadow root as focus zone when available.
|
|
8895
|
+
const focusZoneElementOrShadowRoot = focusZoneElement.shadowRoot || focusZoneElement;
|
|
8896
|
+
|
|
8897
|
+
// Track whether we added a `tabindex="-1"` so we can restore the original state on teardown.
|
|
8898
|
+
let addedTabIndex = false;
|
|
8899
|
+
|
|
8900
|
+
/** Make the zone element programmatically focusable (so we can fall back to it). */
|
|
8901
|
+
const ensureZoneIsFocusable = () => {
|
|
8902
|
+
if (!focusZoneElement.hasAttribute('tabindex')) {
|
|
8903
|
+
focusZoneElement.setAttribute('tabindex', '-1');
|
|
8904
|
+
addedTabIndex = true;
|
|
8855
8905
|
}
|
|
8906
|
+
};
|
|
8856
8907
|
|
|
8857
|
-
|
|
8858
|
-
|
|
8908
|
+
/** Focus the zone element itself as a last-resort fallback. */
|
|
8909
|
+
const focusZoneFallback = () => {
|
|
8910
|
+
ensureZoneIsFocusable();
|
|
8911
|
+
focusZoneElement.focus({
|
|
8912
|
+
preventScroll: true
|
|
8913
|
+
});
|
|
8914
|
+
};
|
|
8859
8915
|
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
return;
|
|
8867
|
-
}
|
|
8868
|
-
const focusable = getFirstAndLastFocusable(focusZoneElementOrShadowRoot);
|
|
8916
|
+
// Trap 'Tab' key down focus switch into the focus zone.
|
|
8917
|
+
const trapTabFocusInFocusZone = evt => {
|
|
8918
|
+
if (evt.key !== 'Tab') {
|
|
8919
|
+
return;
|
|
8920
|
+
}
|
|
8921
|
+
const focusable = getFirstAndLastFocusable(focusZoneElementOrShadowRoot);
|
|
8869
8922
|
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
}
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8923
|
+
// Prevent focus switch if no focusable available — pin focus on the zone itself.
|
|
8924
|
+
if (!focusable.first) {
|
|
8925
|
+
evt.preventDefault();
|
|
8926
|
+
focusZoneFallback();
|
|
8927
|
+
return;
|
|
8928
|
+
}
|
|
8929
|
+
const activeElement = focusZoneElement.shadowRoot ? focusZoneElement.shadowRoot.activeElement : DOCUMENT?.activeElement;
|
|
8930
|
+
if (
|
|
8931
|
+
// No previous focus.
|
|
8932
|
+
!activeElement ||
|
|
8933
|
+
// Previous focus is at the end of the focus zone.
|
|
8934
|
+
!evt.shiftKey && activeElement === focusable.last ||
|
|
8935
|
+
// Previous focus is outside the focus zone.
|
|
8936
|
+
!focusZoneElementOrShadowRoot.contains(activeElement)) {
|
|
8937
|
+
focusable.first.focus();
|
|
8938
|
+
evt.preventDefault();
|
|
8939
|
+
return;
|
|
8940
|
+
}
|
|
8941
|
+
if (
|
|
8942
|
+
// Focus order reversed.
|
|
8943
|
+
evt.shiftKey &&
|
|
8944
|
+
// Previous focus is at the start of the focus zone.
|
|
8945
|
+
activeElement === focusable.first) {
|
|
8946
|
+
focusable.last.focus();
|
|
8947
|
+
evt.preventDefault();
|
|
8948
|
+
}
|
|
8949
|
+
};
|
|
8950
|
+
const focusTrap = {
|
|
8951
|
+
enable: () => rootElement.addEventListener('keydown', trapTabFocusInFocusZone),
|
|
8952
|
+
disable: () => rootElement.removeEventListener('keydown', trapTabFocusInFocusZone)
|
|
8953
|
+
};
|
|
8900
8954
|
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8955
|
+
// SETUP: focus initial element.
|
|
8956
|
+
if (focusElement && focusZoneElementOrShadowRoot.contains(focusElement)) {
|
|
8957
|
+
// Focus the given element.
|
|
8958
|
+
focusElement.focus({
|
|
8959
|
+
preventScroll: true
|
|
8960
|
+
});
|
|
8961
|
+
} else {
|
|
8962
|
+
const firstFocusable = getFirstAndLastFocusable(focusZoneElementOrShadowRoot).first;
|
|
8963
|
+
if (firstFocusable) {
|
|
8964
|
+
// Focus the first focusable descendant.
|
|
8965
|
+
firstFocusable.focus({
|
|
8905
8966
|
preventScroll: true
|
|
8906
8967
|
});
|
|
8907
8968
|
} else {
|
|
8908
|
-
//
|
|
8909
|
-
|
|
8910
|
-
preventScroll: true
|
|
8911
|
-
});
|
|
8969
|
+
// No focusable descendant — fall back to the zone itself (e.g. an empty dialog).
|
|
8970
|
+
focusZoneFallback();
|
|
8912
8971
|
}
|
|
8913
|
-
|
|
8972
|
+
}
|
|
8973
|
+
FOCUS_TRAPS.register(focusTrap);
|
|
8974
|
+
|
|
8975
|
+
// TEARDOWN.
|
|
8976
|
+
signal.addEventListener('abort', () => {
|
|
8977
|
+
FOCUS_TRAPS.unregister(focusTrap);
|
|
8978
|
+
if (addedTabIndex) {
|
|
8979
|
+
focusZoneElement.removeAttribute('tabindex');
|
|
8980
|
+
}
|
|
8981
|
+
}, {
|
|
8982
|
+
once: true
|
|
8983
|
+
});
|
|
8984
|
+
}
|
|
8914
8985
|
|
|
8915
|
-
|
|
8916
|
-
|
|
8986
|
+
/**
|
|
8987
|
+
* Trap 'Tab' focus switch inside the `focusZoneElement`.
|
|
8988
|
+
*
|
|
8989
|
+
* If multiple focus traps are activated, only the last one is maintained and when a focus trap closes, the previous one
|
|
8990
|
+
* gets activated again.
|
|
8991
|
+
*
|
|
8992
|
+
* If the zone has no focusable descendant, the zone element itself receives focus (with a fallback `tabindex="-1"`).
|
|
8993
|
+
*
|
|
8994
|
+
* @param focusZoneElement The element in which to trap the focus.
|
|
8995
|
+
* @param focusElement The element to focus when the focus trap is activated (otherwise the first focusable element
|
|
8996
|
+
* will be focused — and finally the zone element itself if no focusable is found).
|
|
8997
|
+
*/
|
|
8998
|
+
function useFocusTrap(focusZoneElement, focusElement) {
|
|
8999
|
+
useEffect(() => {
|
|
9000
|
+
if (!focusZoneElement) {
|
|
9001
|
+
return undefined;
|
|
9002
|
+
}
|
|
9003
|
+
const controller = new AbortController();
|
|
9004
|
+
setupFocusTrap({
|
|
9005
|
+
focusZoneElement,
|
|
9006
|
+
focusElement
|
|
9007
|
+
}, controller.signal);
|
|
9008
|
+
return () => controller.abort();
|
|
8917
9009
|
}, [focusElement, focusZoneElement]);
|
|
8918
9010
|
}
|
|
8919
9011
|
|
|
@@ -9454,7 +9546,7 @@ _InnerPopover.displayName = COMPONENT_NAME$15;
|
|
|
9454
9546
|
*/
|
|
9455
9547
|
const Popover = skipRender(
|
|
9456
9548
|
// Skip render in SSR
|
|
9457
|
-
() => Boolean(DOCUMENT), _InnerPopover);
|
|
9549
|
+
() => Boolean(DOCUMENT$1), _InnerPopover);
|
|
9458
9550
|
Popover.displayName = COMPONENT_NAME$15;
|
|
9459
9551
|
Popover.className = CLASSNAME$14;
|
|
9460
9552
|
Popover.defaultProps = DEFAULT_PROPS$V;
|
|
@@ -11739,7 +11831,7 @@ const DEFAULT_PROPS$P = {
|
|
|
11739
11831
|
* @return React element.
|
|
11740
11832
|
*/
|
|
11741
11833
|
const Dialog = forwardRef((props, ref) => {
|
|
11742
|
-
if (!DOCUMENT) {
|
|
11834
|
+
if (!DOCUMENT$1) {
|
|
11743
11835
|
// Can't render in SSR.
|
|
11744
11836
|
return null;
|
|
11745
11837
|
}
|
|
@@ -14148,7 +14240,7 @@ const Lightbox = forwardRef((props, ref) => {
|
|
|
14148
14240
|
zIndex,
|
|
14149
14241
|
...forwardedProps
|
|
14150
14242
|
} = props;
|
|
14151
|
-
if (!DOCUMENT) {
|
|
14243
|
+
if (!DOCUMENT$1) {
|
|
14152
14244
|
// Can't render in SSR.
|
|
14153
14245
|
return null;
|
|
14154
14246
|
}
|
|
@@ -15107,7 +15199,7 @@ const Notification = forwardRef((props, ref) => {
|
|
|
15107
15199
|
style,
|
|
15108
15200
|
...forwardedProps
|
|
15109
15201
|
} = props;
|
|
15110
|
-
if (!DOCUMENT) {
|
|
15202
|
+
if (!DOCUMENT$1) {
|
|
15111
15203
|
// Can't render in SSR.
|
|
15112
15204
|
return null;
|
|
15113
15205
|
}
|