@vitus-labs/elements 2.0.0-alpha.27 → 2.0.0-alpha.29
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/README.md +6 -2
- package/lib/index.d.ts +25 -12
- package/lib/index.js +336 -260
- package/lib/vitus-labs-elements.native.js +129 -747
- package/package.json +22 -12
- package/LICENSE +0 -21
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Provider, alignContent, extendCss, makeItResponsive, value } from "@vitus-labs/unistyle";
|
|
2
2
|
import { config, context, isEmpty, omit, pick, render, throttle } from "@vitus-labs/core";
|
|
3
|
-
import { Children, createContext,
|
|
3
|
+
import { Children, createContext, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { isFragment } from "react-is";
|
|
6
6
|
import { createPortal } from "react-dom";
|
|
@@ -150,14 +150,12 @@ const childFixCSS = `
|
|
|
150
150
|
const parentFixCSS = `
|
|
151
151
|
flex-direction: column;
|
|
152
152
|
`;
|
|
153
|
-
const parentFixBlockCSS = `
|
|
154
|
-
width: 100%;
|
|
155
|
-
`;
|
|
156
153
|
const fullHeightCSS = `
|
|
157
154
|
height: 100%;
|
|
158
155
|
`;
|
|
159
156
|
const blockCSS = `
|
|
160
157
|
align-self: stretch;
|
|
158
|
+
width: 100%;
|
|
161
159
|
`;
|
|
162
160
|
const childFixPosition = (isBlock) => `display: ${isBlock ? "flex" : "inline-flex"};`;
|
|
163
161
|
const styles$1 = ({ theme: t, css }) => css`
|
|
@@ -170,9 +168,9 @@ const styles$1 = ({ theme: t, css }) => css`
|
|
|
170
168
|
})};
|
|
171
169
|
|
|
172
170
|
${t.block && blockCSS};
|
|
171
|
+
${t.alignY === "block" && t.block && fullHeightCSS};
|
|
173
172
|
|
|
174
173
|
${!t.childFix && childFixPosition(t.block)};
|
|
175
|
-
${t.parentFix && t.block && parentFixBlockCSS};
|
|
176
174
|
${t.parentFix && parentFixCSS};
|
|
177
175
|
|
|
178
176
|
${t.extraStyles && extendCss(t.extraStyles)};
|
|
@@ -216,13 +214,12 @@ const isWebFixNeeded = (tag) => {
|
|
|
216
214
|
//#region src/helpers/Wrapper/component.tsx
|
|
217
215
|
/**
|
|
218
216
|
* Wrapper component that serves as the outermost styled container for Element.
|
|
219
|
-
*
|
|
220
|
-
* detects button/fieldset/legend tags and applies a two-layer flex fix
|
|
217
|
+
* On web, it detects button/fieldset/legend tags and applies a two-layer flex fix
|
|
221
218
|
* (parent + child Styled) because these HTML elements do not natively
|
|
222
219
|
* support `display: flex` consistently across browsers.
|
|
223
220
|
*/
|
|
224
221
|
const DEV_PROPS = IS_DEVELOPMENT ? { "data-vl-element": "Element" } : {};
|
|
225
|
-
const Component$8 =
|
|
222
|
+
const Component$8 = ({ children, ref, tag, block, extendCss, direction, alignX, alignY, equalCols, isInline, ...props }) => {
|
|
226
223
|
const COMMON_PROPS = {
|
|
227
224
|
...props,
|
|
228
225
|
...DEV_PROPS,
|
|
@@ -278,7 +275,7 @@ const Component$8 = forwardRef(({ children, tag, block, extendCss, direction, al
|
|
|
278
275
|
children
|
|
279
276
|
})
|
|
280
277
|
});
|
|
281
|
-
}
|
|
278
|
+
};
|
|
282
279
|
|
|
283
280
|
//#endregion
|
|
284
281
|
//#region src/helpers/Wrapper/index.ts
|
|
@@ -388,7 +385,7 @@ const defaultDirection = "inline";
|
|
|
388
385
|
const defaultContentDirection = "rows";
|
|
389
386
|
const defaultAlignX = "left";
|
|
390
387
|
const defaultAlignY = "center";
|
|
391
|
-
const Component$7 =
|
|
388
|
+
const Component$7 = ({ innerRef, ref, tag, label, content, children, beforeContent, afterContent, equalBeforeAfter, block, equalCols, gap, direction, alignX = defaultAlignX, alignY = defaultAlignY, css, contentCss, beforeContentCss, afterContentCss, contentDirection = defaultContentDirection, contentAlignX = defaultAlignX, contentAlignY = defaultAlignY, beforeContentDirection = defaultDirection, beforeContentAlignX = defaultAlignX, beforeContentAlignY = defaultAlignY, afterContentDirection = defaultDirection, afterContentAlignX = defaultAlignX, afterContentAlignY = defaultAlignY, ...props }) => {
|
|
392
389
|
const shouldBeEmpty = !!props.dangerouslySetInnerHTML || getShouldBeEmpty(tag);
|
|
393
390
|
const isSimpleElement = !beforeContent && !afterContent;
|
|
394
391
|
const CHILDREN = children ?? content ?? label;
|
|
@@ -427,7 +424,13 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
|
|
|
427
424
|
}, [externalRef]);
|
|
428
425
|
useLayoutEffect(() => {
|
|
429
426
|
if (!equalBeforeAfter || !beforeContent || !afterContent) return;
|
|
430
|
-
|
|
427
|
+
const node = equalizeRef.current;
|
|
428
|
+
if (!node) return;
|
|
429
|
+
equalize(node, direction);
|
|
430
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
431
|
+
const observer = new ResizeObserver(() => equalize(node, direction));
|
|
432
|
+
observer.observe(node);
|
|
433
|
+
return () => observer.disconnect();
|
|
431
434
|
}, [
|
|
432
435
|
equalBeforeAfter,
|
|
433
436
|
beforeContent,
|
|
@@ -490,7 +493,7 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
|
|
|
490
493
|
})
|
|
491
494
|
]
|
|
492
495
|
});
|
|
493
|
-
}
|
|
496
|
+
};
|
|
494
497
|
const name$5 = `${PKG_NAME}/Element`;
|
|
495
498
|
Component$7.displayName = name$5;
|
|
496
499
|
Component$7.pkgName = PKG_NAME;
|
|
@@ -510,27 +513,6 @@ var Element_default = Component$7;
|
|
|
510
513
|
* wrapped with `wrapComponent`. Children always take priority over the
|
|
511
514
|
* component+data prop pattern.
|
|
512
515
|
*/
|
|
513
|
-
const classifyData = (data) => {
|
|
514
|
-
const items = data.filter((item) => item != null && !(typeof item === "object" && isEmpty(item)));
|
|
515
|
-
if (items.length === 0) return null;
|
|
516
|
-
let isSimple = true;
|
|
517
|
-
let isComplex = true;
|
|
518
|
-
for (const item of items) if (typeof item === "string" || typeof item === "number") isComplex = false;
|
|
519
|
-
else if (typeof item === "object") isSimple = false;
|
|
520
|
-
else {
|
|
521
|
-
isSimple = false;
|
|
522
|
-
isComplex = false;
|
|
523
|
-
}
|
|
524
|
-
if (isSimple) return {
|
|
525
|
-
type: "simple",
|
|
526
|
-
data: items
|
|
527
|
-
};
|
|
528
|
-
if (isComplex) return {
|
|
529
|
-
type: "complex",
|
|
530
|
-
data: items
|
|
531
|
-
};
|
|
532
|
-
return null;
|
|
533
|
-
};
|
|
534
516
|
const RESERVED_PROPS = [
|
|
535
517
|
"children",
|
|
536
518
|
"component",
|
|
@@ -541,7 +523,7 @@ const RESERVED_PROPS = [
|
|
|
541
523
|
"itemProps",
|
|
542
524
|
"wrapProps"
|
|
543
525
|
];
|
|
544
|
-
const
|
|
526
|
+
const buildExtendedProps = (i, length) => {
|
|
545
527
|
const position = i + 1;
|
|
546
528
|
return {
|
|
547
529
|
index: i,
|
|
@@ -552,106 +534,96 @@ const attachItemProps = ({ i, length }) => {
|
|
|
552
534
|
position
|
|
553
535
|
};
|
|
554
536
|
};
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const renderChild = (child, total = 1, i = 0) => {
|
|
564
|
-
if (!itemProps && !Wrapper) return child;
|
|
565
|
-
const extendedProps = attachItemProps({
|
|
566
|
-
i,
|
|
567
|
-
length: total
|
|
568
|
-
});
|
|
569
|
-
const finalItemProps = itemProps ? injectItemProps({}, extendedProps) : {};
|
|
570
|
-
if (Wrapper) return /* @__PURE__ */ jsx(Wrapper, {
|
|
571
|
-
...wrapProps ? injectWrapItemProps({}, extendedProps) : {},
|
|
572
|
-
children: render(child, finalItemProps)
|
|
573
|
-
}, i);
|
|
574
|
-
return render(child, {
|
|
575
|
-
key: i,
|
|
576
|
-
...finalItemProps
|
|
577
|
-
});
|
|
578
|
-
};
|
|
579
|
-
const renderChildren = () => {
|
|
580
|
-
if (!children) return null;
|
|
581
|
-
if (Array.isArray(children)) return Children.map(children, (item, i) => renderChild(item, children.length, i));
|
|
582
|
-
if (isFragment(children)) {
|
|
583
|
-
const fragmentChildren = children.props.children;
|
|
584
|
-
const childrenLength = fragmentChildren.length;
|
|
585
|
-
return fragmentChildren.map((item, i) => renderChild(item, childrenLength, i));
|
|
586
|
-
}
|
|
587
|
-
return renderChild(children);
|
|
588
|
-
};
|
|
589
|
-
const renderSimpleArray = (data) => {
|
|
590
|
-
const { length } = data;
|
|
591
|
-
if (length === 0) return null;
|
|
592
|
-
return data.map((item, i) => {
|
|
593
|
-
const key = getKey(item, i);
|
|
594
|
-
const keyName = valueName ?? "children";
|
|
595
|
-
const extendedProps = attachItemProps({
|
|
596
|
-
i,
|
|
597
|
-
length
|
|
598
|
-
});
|
|
599
|
-
const finalItemProps = {
|
|
600
|
-
...itemProps ? injectItemProps({ [keyName]: item }, extendedProps) : {},
|
|
601
|
-
[keyName]: item
|
|
602
|
-
};
|
|
603
|
-
if (Wrapper) return /* @__PURE__ */ jsx(Wrapper, {
|
|
604
|
-
...wrapProps ? injectWrapItemProps({ [keyName]: item }, extendedProps) : {},
|
|
605
|
-
children: render(component, finalItemProps)
|
|
606
|
-
}, key);
|
|
607
|
-
return render(component, {
|
|
608
|
-
key,
|
|
609
|
-
...finalItemProps
|
|
610
|
-
});
|
|
611
|
-
});
|
|
612
|
-
};
|
|
613
|
-
const getObjectKey = (item, index) => {
|
|
614
|
-
if (!itemKey) return item.key ?? item.id ?? item.itemId ?? index;
|
|
615
|
-
if (typeof itemKey === "function") return itemKey(item, index);
|
|
616
|
-
if (typeof itemKey === "string") return item[itemKey];
|
|
617
|
-
return index;
|
|
537
|
+
const resolveCallback = (cb, source, ext) => {
|
|
538
|
+
if (!cb) return {};
|
|
539
|
+
return typeof cb === "function" ? cb(source, ext) : cb;
|
|
540
|
+
};
|
|
541
|
+
const renderSpec = (spec, ext, itemProps, wrapProps, Wrapper) => {
|
|
542
|
+
const finalItemProps = {
|
|
543
|
+
...resolveCallback(itemProps, spec.source, ext),
|
|
544
|
+
...spec.base
|
|
618
545
|
};
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const extendedProps = attachItemProps({
|
|
627
|
-
i,
|
|
628
|
-
length
|
|
629
|
-
});
|
|
630
|
-
const finalItemProps = {
|
|
631
|
-
...itemProps ? injectItemProps(item, extendedProps) : {},
|
|
632
|
-
...restItem
|
|
633
|
-
};
|
|
634
|
-
if (Wrapper && !itemComponent) return /* @__PURE__ */ jsx(Wrapper, {
|
|
635
|
-
...wrapProps ? injectWrapItemProps(item, extendedProps) : {},
|
|
636
|
-
children: render(renderItem, finalItemProps)
|
|
637
|
-
}, key);
|
|
638
|
-
return render(renderItem, {
|
|
639
|
-
key,
|
|
640
|
-
...finalItemProps
|
|
641
|
-
});
|
|
642
|
-
});
|
|
546
|
+
if (Wrapper && !spec.skipWrap) return /* @__PURE__ */ jsx(Wrapper, {
|
|
547
|
+
...resolveCallback(wrapProps, spec.source, ext),
|
|
548
|
+
children: spec.isNode ? render(spec.target, finalItemProps) : render(spec.target, finalItemProps)
|
|
549
|
+
}, spec.key);
|
|
550
|
+
const propsWithKey = {
|
|
551
|
+
key: spec.key,
|
|
552
|
+
...finalItemProps
|
|
643
553
|
};
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
554
|
+
return spec.isNode ? render(spec.target, propsWithKey) : render(spec.target, propsWithKey);
|
|
555
|
+
};
|
|
556
|
+
/** Normalize children (single, array, or fragment) into an array of nodes. */
|
|
557
|
+
const flattenChildren = (children) => {
|
|
558
|
+
if (Array.isArray(children)) return children;
|
|
559
|
+
if (isFragment(children)) return children.props.children;
|
|
560
|
+
return [children];
|
|
561
|
+
};
|
|
562
|
+
/** Drop nullish entries and empty objects (matches legacy behavior). */
|
|
563
|
+
const filterValidItems = (data) => data.filter((item) => item != null && !(typeof item === "object" && isEmpty(item)));
|
|
564
|
+
/** Determine if the array is uniformly simple (string/number) or complex (object). Mixed → null. */
|
|
565
|
+
const detectKind = (items) => {
|
|
566
|
+
let kind = null;
|
|
567
|
+
for (const item of items) {
|
|
568
|
+
const t = typeof item === "string" || typeof item === "number" ? "simple" : typeof item === "object" ? "complex" : null;
|
|
569
|
+
if (t === null) return null;
|
|
570
|
+
if (kind === null) kind = t;
|
|
571
|
+
else if (kind !== t) return null;
|
|
572
|
+
}
|
|
573
|
+
return kind;
|
|
574
|
+
};
|
|
575
|
+
const objectKey = (item, index, itemKey) => {
|
|
576
|
+
if (!itemKey) return item.key ?? item.id ?? item.itemId ?? index;
|
|
577
|
+
if (typeof itemKey === "function") return itemKey(item, index);
|
|
578
|
+
if (typeof itemKey === "string") return item[itemKey] ?? index;
|
|
579
|
+
return index;
|
|
580
|
+
};
|
|
581
|
+
const buildChildrenSpecs = (children) => flattenChildren(children).map((node, i) => ({
|
|
582
|
+
key: i,
|
|
583
|
+
target: node,
|
|
584
|
+
source: {},
|
|
585
|
+
base: {},
|
|
586
|
+
isNode: true,
|
|
587
|
+
skipWrap: false
|
|
588
|
+
}));
|
|
589
|
+
const buildSimpleSpecs = (items, component, valueName, itemKey) => {
|
|
590
|
+
const keyName = valueName ?? "children";
|
|
591
|
+
return items.map((value, i) => ({
|
|
592
|
+
key: typeof itemKey === "function" ? itemKey(value, i) : i,
|
|
593
|
+
target: component,
|
|
594
|
+
source: { [keyName]: value },
|
|
595
|
+
base: { [keyName]: value },
|
|
596
|
+
isNode: false,
|
|
597
|
+
skipWrap: false
|
|
598
|
+
}));
|
|
599
|
+
};
|
|
600
|
+
const buildObjectSpecs = (items, component, itemKey) => items.map((item, i) => {
|
|
601
|
+
const { component: itemComponent, ...rest } = item;
|
|
602
|
+
return {
|
|
603
|
+
key: objectKey(rest, i, itemKey),
|
|
604
|
+
target: itemComponent ?? component,
|
|
605
|
+
source: item,
|
|
606
|
+
base: rest,
|
|
607
|
+
isNode: false,
|
|
608
|
+
skipWrap: Boolean(itemComponent)
|
|
653
609
|
};
|
|
654
|
-
|
|
610
|
+
});
|
|
611
|
+
const buildDataSpecs = (data, component, valueName, itemKey) => {
|
|
612
|
+
const items = filterValidItems(data);
|
|
613
|
+
if (items.length === 0) return null;
|
|
614
|
+
const kind = detectKind(items);
|
|
615
|
+
if (!kind) return null;
|
|
616
|
+
return kind === "simple" ? buildSimpleSpecs(items, component, valueName, itemKey) : buildObjectSpecs(items, component, itemKey);
|
|
617
|
+
};
|
|
618
|
+
const Component$6 = ({ itemKey, valueName, children, component, data, wrapComponent: Wrapper, wrapProps, itemProps }) => {
|
|
619
|
+
let specs = null;
|
|
620
|
+
if (children) {
|
|
621
|
+
if (!Array.isArray(children) && !isFragment(children) && !itemProps && !Wrapper) return children;
|
|
622
|
+
specs = buildChildrenSpecs(children);
|
|
623
|
+
} else if (component && Array.isArray(data)) specs = buildDataSpecs(data, component, valueName, itemKey);
|
|
624
|
+
if (!specs || specs.length === 0) return null;
|
|
625
|
+
const total = specs.length;
|
|
626
|
+
return Children.toArray(specs.map((spec, i) => renderSpec(spec, buildExtendedProps(i, total), itemProps, wrapProps, Wrapper)));
|
|
655
627
|
};
|
|
656
628
|
var component_default = Object.assign(memo(Component$6), {
|
|
657
629
|
isIterator: true,
|
|
@@ -671,7 +643,7 @@ var Iterator_default = component_default;
|
|
|
671
643
|
* is wrapped in an Element that receives all non-iterator props (e.g.,
|
|
672
644
|
* layout, alignment, css), allowing the list to be styled as a single block.
|
|
673
645
|
*/
|
|
674
|
-
const Component$5 =
|
|
646
|
+
const Component$5 = ({ rootElement = false, ref, ...props }) => {
|
|
675
647
|
const renderedList = /* @__PURE__ */ jsx(Iterator_default, { ...pick(props, Iterator_default.RESERVED_PROPS) });
|
|
676
648
|
if (!rootElement) return renderedList;
|
|
677
649
|
return /* @__PURE__ */ jsx(Element_default, {
|
|
@@ -679,7 +651,7 @@ const Component$5 = forwardRef(({ rootElement = false, ...props }, ref) => {
|
|
|
679
651
|
...omit(props, Iterator_default.RESERVED_PROPS),
|
|
680
652
|
children: renderedList
|
|
681
653
|
});
|
|
682
|
-
}
|
|
654
|
+
};
|
|
683
655
|
const name$4 = `${PKG_NAME}/List`;
|
|
684
656
|
Component$5.displayName = name$4;
|
|
685
657
|
Component$5.pkgName = PKG_NAME;
|
|
@@ -748,14 +720,12 @@ const Component = ({ children, blocked, setBlocked, setUnblocked }) => {
|
|
|
748
720
|
};
|
|
749
721
|
|
|
750
722
|
//#endregion
|
|
751
|
-
//#region src/Overlay/
|
|
723
|
+
//#region src/Overlay/positionMath.ts
|
|
752
724
|
/**
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
* Event handlers are throttled for performance, and nested overlay blocking
|
|
758
|
-
* is coordinated through the overlay context.
|
|
725
|
+
* Pure positioning math for the Overlay component. No React, no DOM mutations.
|
|
726
|
+
* Given the current trigger and content rects (plus alignment options), computes
|
|
727
|
+
* the final viewport-relative position and the alignment that was actually used
|
|
728
|
+
* (which may differ from the requested alignment when an edge would be clipped).
|
|
759
729
|
*/
|
|
760
730
|
const sel = (cond, a, b) => cond ? a : b;
|
|
761
731
|
const devWarn = (msg) => {
|
|
@@ -925,24 +895,192 @@ const processVisibilityEvent = (e, active, openOn, closeOn, isTrigger, isContent
|
|
|
925
895
|
else if (closeOn === "clickOnTrigger" && isTrigger(e)) hideContent();
|
|
926
896
|
else if (closeOn === "clickOutsideContent" && !isContent(e)) hideContent();
|
|
927
897
|
};
|
|
928
|
-
|
|
898
|
+
|
|
899
|
+
//#endregion
|
|
900
|
+
//#region src/Overlay/useEscapeKey.ts
|
|
901
|
+
/** Closes the overlay on Escape keypress when `enabled` and `active`. */
|
|
902
|
+
const useEscapeKey = (enabled, active, blocked, hide) => {
|
|
903
|
+
useEffect(() => {
|
|
904
|
+
if (!enabled || !active || blocked) return void 0;
|
|
905
|
+
const onKey = (e) => {
|
|
906
|
+
if (e.key === "Escape") hide();
|
|
907
|
+
};
|
|
908
|
+
window.addEventListener("keydown", onKey);
|
|
909
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
910
|
+
}, [
|
|
911
|
+
enabled,
|
|
912
|
+
active,
|
|
913
|
+
blocked,
|
|
914
|
+
hide
|
|
915
|
+
]);
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
//#endregion
|
|
919
|
+
//#region src/Overlay/useHoverListeners.ts
|
|
920
|
+
/**
|
|
921
|
+
* Hover-based open/close. Uses mouseenter/mouseleave on trigger + content
|
|
922
|
+
* (instead of window-level mousemove) and a configurable delay to bridge
|
|
923
|
+
* the gap between trigger and content elements without flicker.
|
|
924
|
+
*/
|
|
925
|
+
const useHoverListeners = ({ triggerRef, contentRef, isContentLoaded, active, blocked, disabled, openOn, closeOn, hoverDelay, showContent, hideContent }) => {
|
|
926
|
+
const hoverTimeoutRef = useRef(null);
|
|
927
|
+
useEffect(() => {
|
|
928
|
+
if (blocked || disabled || !(openOn === "hover" || closeOn === "hover")) return void 0;
|
|
929
|
+
const trigger = triggerRef.current;
|
|
930
|
+
const content = contentRef.current;
|
|
931
|
+
const clearHoverTimeout = () => {
|
|
932
|
+
if (hoverTimeoutRef.current != null) {
|
|
933
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
934
|
+
hoverTimeoutRef.current = null;
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
const scheduleHide = () => {
|
|
938
|
+
clearHoverTimeout();
|
|
939
|
+
hoverTimeoutRef.current = setTimeout(hideContent, hoverDelay);
|
|
940
|
+
};
|
|
941
|
+
const onTriggerEnter = () => {
|
|
942
|
+
clearHoverTimeout();
|
|
943
|
+
if (openOn === "hover" && !active) showContent();
|
|
944
|
+
};
|
|
945
|
+
const onTriggerLeave = () => {
|
|
946
|
+
if (closeOn === "hover" && active) scheduleHide();
|
|
947
|
+
};
|
|
948
|
+
const onContentEnter = () => {
|
|
949
|
+
clearHoverTimeout();
|
|
950
|
+
};
|
|
951
|
+
const onContentLeave = () => {
|
|
952
|
+
if (closeOn === "hover" && active) scheduleHide();
|
|
953
|
+
};
|
|
954
|
+
if (trigger) {
|
|
955
|
+
trigger.addEventListener("mouseenter", onTriggerEnter);
|
|
956
|
+
trigger.addEventListener("mouseleave", onTriggerLeave);
|
|
957
|
+
}
|
|
958
|
+
if (content) {
|
|
959
|
+
content.addEventListener("mouseenter", onContentEnter);
|
|
960
|
+
content.addEventListener("mouseleave", onContentLeave);
|
|
961
|
+
}
|
|
962
|
+
return () => {
|
|
963
|
+
clearHoverTimeout();
|
|
964
|
+
if (trigger) {
|
|
965
|
+
trigger.removeEventListener("mouseenter", onTriggerEnter);
|
|
966
|
+
trigger.removeEventListener("mouseleave", onTriggerLeave);
|
|
967
|
+
}
|
|
968
|
+
if (content) {
|
|
969
|
+
content.removeEventListener("mouseenter", onContentEnter);
|
|
970
|
+
content.removeEventListener("mouseleave", onContentLeave);
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
}, [
|
|
974
|
+
active,
|
|
975
|
+
isContentLoaded,
|
|
976
|
+
blocked,
|
|
977
|
+
disabled,
|
|
978
|
+
openOn,
|
|
979
|
+
closeOn,
|
|
980
|
+
hoverDelay,
|
|
981
|
+
showContent,
|
|
982
|
+
hideContent
|
|
983
|
+
]);
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
//#endregion
|
|
987
|
+
//#region src/Overlay/useScrollReposition.ts
|
|
988
|
+
let modalOverflowCount = 0;
|
|
989
|
+
/**
|
|
990
|
+
* Window-level scroll/resize listeners that reposition active overlays and
|
|
991
|
+
* re-evaluate close-on-scroll behavior. Also manages the body overflow lock
|
|
992
|
+
* for modal overlays (refcounted across nested modals).
|
|
993
|
+
*/
|
|
994
|
+
const useWindowReposition = (active, type, handleContentPosition, handleVisibility) => {
|
|
995
|
+
useEffect(() => {
|
|
996
|
+
if (!active) return void 0;
|
|
997
|
+
const shouldSetOverflow = type === "modal";
|
|
998
|
+
const onScroll = (e) => {
|
|
999
|
+
handleContentPosition();
|
|
1000
|
+
handleVisibility(e);
|
|
1001
|
+
};
|
|
1002
|
+
if (shouldSetOverflow) {
|
|
1003
|
+
modalOverflowCount++;
|
|
1004
|
+
if (modalOverflowCount === 1) document.body.style.overflow = "hidden";
|
|
1005
|
+
}
|
|
1006
|
+
window.addEventListener("resize", handleContentPosition);
|
|
1007
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
1008
|
+
return () => {
|
|
1009
|
+
handleContentPosition.cancel();
|
|
1010
|
+
handleVisibility.cancel();
|
|
1011
|
+
if (shouldSetOverflow) {
|
|
1012
|
+
modalOverflowCount--;
|
|
1013
|
+
if (modalOverflowCount === 0) document.body.style.overflow = "";
|
|
1014
|
+
}
|
|
1015
|
+
window.removeEventListener("resize", handleContentPosition);
|
|
1016
|
+
window.removeEventListener("scroll", onScroll);
|
|
1017
|
+
};
|
|
1018
|
+
}, [
|
|
1019
|
+
active,
|
|
1020
|
+
type,
|
|
1021
|
+
handleContentPosition,
|
|
1022
|
+
handleVisibility
|
|
1023
|
+
]);
|
|
1024
|
+
};
|
|
1025
|
+
/**
|
|
1026
|
+
* Same as `useWindowReposition` but for a custom scrollable ancestor.
|
|
1027
|
+
* Locks the parent's overflow while the overlay is active (unless hover-driven,
|
|
1028
|
+
* which expects the parent to keep scrolling).
|
|
1029
|
+
*/
|
|
1030
|
+
const useParentContainerReposition = (active, parentContainer, closeOn, handleContentPosition, handleVisibility) => {
|
|
1031
|
+
useEffect(() => {
|
|
1032
|
+
if (!active || !parentContainer) return void 0;
|
|
1033
|
+
if (closeOn !== "hover") parentContainer.style.overflow = "hidden";
|
|
1034
|
+
const onScroll = (e) => {
|
|
1035
|
+
handleContentPosition();
|
|
1036
|
+
handleVisibility(e);
|
|
1037
|
+
};
|
|
1038
|
+
parentContainer.addEventListener("scroll", onScroll, { passive: true });
|
|
1039
|
+
return () => {
|
|
1040
|
+
parentContainer.style.overflow = "";
|
|
1041
|
+
parentContainer.removeEventListener("scroll", onScroll);
|
|
1042
|
+
};
|
|
1043
|
+
}, [
|
|
1044
|
+
active,
|
|
1045
|
+
parentContainer,
|
|
1046
|
+
closeOn,
|
|
1047
|
+
handleContentPosition,
|
|
1048
|
+
handleVisibility
|
|
1049
|
+
]);
|
|
1050
|
+
};
|
|
1051
|
+
const useScrollReposition = ({ active, type, parentContainer, closeOn, handleContentPosition, handleVisibility }) => {
|
|
1052
|
+
useWindowReposition(active, type, handleContentPosition, handleVisibility);
|
|
1053
|
+
useParentContainerReposition(active, parentContainer, closeOn, handleContentPosition, handleVisibility);
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
//#endregion
|
|
1057
|
+
//#region src/Overlay/useOverlay.tsx
|
|
1058
|
+
/**
|
|
1059
|
+
* Core hook powering the Overlay component. Manages open/close state, DOM
|
|
1060
|
+
* event listeners (click, hover, scroll, resize, ESC key), and dynamic
|
|
1061
|
+
* positioning of overlay content relative to its trigger. Supports dropdown,
|
|
1062
|
+
* tooltip, popover, and modal types with automatic edge-of-viewport flipping.
|
|
1063
|
+
*
|
|
1064
|
+
* Pure positioning math lives in `./positionMath`. Event-listener concerns
|
|
1065
|
+
* live in dedicated hooks: `./useEscapeKey`, `./useHoverListeners`,
|
|
1066
|
+
* `./useScrollReposition`.
|
|
1067
|
+
*/
|
|
1068
|
+
const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type = "dropdown", position = "fixed", align = "bottom", alignX = "left", alignY = "bottom", offsetX = 0, offsetY = 0, throttleDelay = 200, parentContainer, closeOnEsc = true, hoverDelay = 100, disabled, onOpen, onClose } = {}) => {
|
|
929
1069
|
const { rootSize } = useContext(context);
|
|
930
1070
|
const ctx = useOverlayContext();
|
|
931
1071
|
const [isContentLoaded, setContentLoaded] = useState(false);
|
|
932
1072
|
const [innerAlignX, setInnerAlignX] = useState(alignX);
|
|
933
1073
|
const [innerAlignY, setInnerAlignY] = useState(alignY);
|
|
934
|
-
const [
|
|
935
|
-
const
|
|
1074
|
+
const [blockedCount, setBlockedCount] = useState(0);
|
|
1075
|
+
const blocked = blockedCount > 0;
|
|
1076
|
+
const [active, setActive] = useState(isOpen);
|
|
936
1077
|
const triggerRef = useRef(null);
|
|
937
1078
|
const contentRef = useRef(null);
|
|
938
|
-
const
|
|
939
|
-
const
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const hideContent = useCallback(() => {
|
|
944
|
-
handleActive(false);
|
|
945
|
-
}, []);
|
|
1079
|
+
const prevFocusRef = useRef(null);
|
|
1080
|
+
const setBlocked = useCallback(() => setBlockedCount((c) => c + 1), []);
|
|
1081
|
+
const setUnblocked = useCallback(() => setBlockedCount((c) => Math.max(0, c - 1)), []);
|
|
1082
|
+
const showContent = useCallback(() => setActive(true), []);
|
|
1083
|
+
const hideContent = useCallback(() => setActive(false), []);
|
|
946
1084
|
const getAncestorOffset = useCallback(() => {
|
|
947
1085
|
if (position !== "absolute" || !contentRef.current) return {
|
|
948
1086
|
top: 0,
|
|
@@ -1011,7 +1149,7 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
|
|
|
1011
1149
|
const latestHandleVisibility = useRef(handleVisibilityByEventType);
|
|
1012
1150
|
latestHandleVisibility.current = handleVisibilityByEventType;
|
|
1013
1151
|
const handleContentPosition = useMemo(() => throttle(() => latestSetContentPosition.current(), throttleDelay), [throttleDelay]);
|
|
1014
|
-
const handleClick =
|
|
1152
|
+
const handleClick = useCallback((e) => latestHandleVisibility.current(e), []);
|
|
1015
1153
|
const handleVisibility = useMemo(() => throttle((e) => latestHandleVisibility.current(e), throttleDelay), [throttleDelay]);
|
|
1016
1154
|
useEffect(() => {
|
|
1017
1155
|
setInnerAlignX(alignX);
|
|
@@ -1058,60 +1196,30 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
|
|
|
1058
1196
|
onOpen
|
|
1059
1197
|
]);
|
|
1060
1198
|
useEffect(() => {
|
|
1061
|
-
if (
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1199
|
+
if (type !== "modal") return;
|
|
1200
|
+
if (active && isContentLoaded && contentRef.current) {
|
|
1201
|
+
prevFocusRef.current = document.activeElement;
|
|
1202
|
+
if (contentRef.current.tabIndex < 0) contentRef.current.tabIndex = -1;
|
|
1203
|
+
contentRef.current.focus();
|
|
1204
|
+
}
|
|
1205
|
+
if (!active && prevFocusRef.current) {
|
|
1206
|
+
prevFocusRef.current.focus();
|
|
1207
|
+
prevFocusRef.current = null;
|
|
1208
|
+
}
|
|
1069
1209
|
}, [
|
|
1070
1210
|
active,
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
hideContent
|
|
1211
|
+
isContentLoaded,
|
|
1212
|
+
type
|
|
1074
1213
|
]);
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
const shouldSetOverflow = type === "modal";
|
|
1078
|
-
const onScroll = (e) => {
|
|
1079
|
-
handleContentPosition();
|
|
1080
|
-
handleVisibility(e);
|
|
1081
|
-
};
|
|
1082
|
-
if (shouldSetOverflow) document.body.style.overflow = "hidden";
|
|
1083
|
-
window.addEventListener("resize", handleContentPosition);
|
|
1084
|
-
window.addEventListener("scroll", onScroll, { passive: true });
|
|
1085
|
-
return () => {
|
|
1086
|
-
if (shouldSetOverflow) document.body.style.overflow = "";
|
|
1087
|
-
window.removeEventListener("resize", handleContentPosition);
|
|
1088
|
-
window.removeEventListener("scroll", onScroll);
|
|
1089
|
-
};
|
|
1090
|
-
}, [
|
|
1214
|
+
useEscapeKey(closeOnEsc, active, blocked, hideContent);
|
|
1215
|
+
useScrollReposition({
|
|
1091
1216
|
active,
|
|
1092
1217
|
type,
|
|
1093
|
-
handleVisibility,
|
|
1094
|
-
handleContentPosition
|
|
1095
|
-
]);
|
|
1096
|
-
useEffect(() => {
|
|
1097
|
-
if (!active || !parentContainer) return void 0;
|
|
1098
|
-
if (closeOn !== "hover") parentContainer.style.overflow = "hidden";
|
|
1099
|
-
const onScroll = (e) => {
|
|
1100
|
-
handleContentPosition();
|
|
1101
|
-
handleVisibility(e);
|
|
1102
|
-
};
|
|
1103
|
-
parentContainer.addEventListener("scroll", onScroll, { passive: true });
|
|
1104
|
-
return () => {
|
|
1105
|
-
parentContainer.style.overflow = "";
|
|
1106
|
-
parentContainer.removeEventListener("scroll", onScroll);
|
|
1107
|
-
};
|
|
1108
|
-
}, [
|
|
1109
|
-
active,
|
|
1110
1218
|
parentContainer,
|
|
1111
1219
|
closeOn,
|
|
1112
1220
|
handleContentPosition,
|
|
1113
1221
|
handleVisibility
|
|
1114
|
-
|
|
1222
|
+
});
|
|
1115
1223
|
useEffect(() => {
|
|
1116
1224
|
if (blocked || disabled) return void 0;
|
|
1117
1225
|
if (openOn === "click" || [
|
|
@@ -1119,9 +1227,7 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
|
|
|
1119
1227
|
"clickOnTrigger",
|
|
1120
1228
|
"clickOutsideContent"
|
|
1121
1229
|
].includes(closeOn)) window.addEventListener("click", handleClick);
|
|
1122
|
-
return () =>
|
|
1123
|
-
window.removeEventListener("click", handleClick);
|
|
1124
|
-
};
|
|
1230
|
+
return () => window.removeEventListener("click", handleClick);
|
|
1125
1231
|
}, [
|
|
1126
1232
|
openOn,
|
|
1127
1233
|
closeOn,
|
|
@@ -1129,70 +1235,24 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
|
|
|
1129
1235
|
disabled,
|
|
1130
1236
|
handleClick
|
|
1131
1237
|
]);
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
const trigger = triggerRef.current;
|
|
1136
|
-
const content = contentRef.current;
|
|
1137
|
-
const clearHoverTimeout = () => {
|
|
1138
|
-
if (hoverTimeoutRef.current != null) {
|
|
1139
|
-
clearTimeout(hoverTimeoutRef.current);
|
|
1140
|
-
hoverTimeoutRef.current = null;
|
|
1141
|
-
}
|
|
1142
|
-
};
|
|
1143
|
-
const scheduleHide = () => {
|
|
1144
|
-
clearHoverTimeout();
|
|
1145
|
-
hoverTimeoutRef.current = setTimeout(hideContent, 100);
|
|
1146
|
-
};
|
|
1147
|
-
const onTriggerEnter = () => {
|
|
1148
|
-
clearHoverTimeout();
|
|
1149
|
-
if (openOn === "hover" && !active) showContent();
|
|
1150
|
-
};
|
|
1151
|
-
const onTriggerLeave = () => {
|
|
1152
|
-
if (closeOn === "hover" && active) scheduleHide();
|
|
1153
|
-
};
|
|
1154
|
-
const onContentEnter = () => {
|
|
1155
|
-
clearHoverTimeout();
|
|
1156
|
-
};
|
|
1157
|
-
const onContentLeave = () => {
|
|
1158
|
-
if (closeOn === "hover" && active) scheduleHide();
|
|
1159
|
-
};
|
|
1160
|
-
if (trigger) {
|
|
1161
|
-
trigger.addEventListener("mouseenter", onTriggerEnter);
|
|
1162
|
-
trigger.addEventListener("mouseleave", onTriggerLeave);
|
|
1163
|
-
}
|
|
1164
|
-
if (content) {
|
|
1165
|
-
content.addEventListener("mouseenter", onContentEnter);
|
|
1166
|
-
content.addEventListener("mouseleave", onContentLeave);
|
|
1167
|
-
}
|
|
1168
|
-
return () => {
|
|
1169
|
-
clearHoverTimeout();
|
|
1170
|
-
if (trigger) {
|
|
1171
|
-
trigger.removeEventListener("mouseenter", onTriggerEnter);
|
|
1172
|
-
trigger.removeEventListener("mouseleave", onTriggerLeave);
|
|
1173
|
-
}
|
|
1174
|
-
if (content) {
|
|
1175
|
-
content.removeEventListener("mouseenter", onContentEnter);
|
|
1176
|
-
content.removeEventListener("mouseleave", onContentLeave);
|
|
1177
|
-
}
|
|
1178
|
-
};
|
|
1179
|
-
}, [
|
|
1180
|
-
active,
|
|
1238
|
+
useHoverListeners({
|
|
1239
|
+
triggerRef,
|
|
1240
|
+
contentRef,
|
|
1181
1241
|
isContentLoaded,
|
|
1242
|
+
active,
|
|
1182
1243
|
blocked,
|
|
1183
1244
|
disabled,
|
|
1184
1245
|
openOn,
|
|
1185
1246
|
closeOn,
|
|
1247
|
+
hoverDelay,
|
|
1186
1248
|
showContent,
|
|
1187
1249
|
hideContent
|
|
1188
|
-
|
|
1250
|
+
});
|
|
1189
1251
|
return {
|
|
1190
1252
|
triggerRef,
|
|
1191
1253
|
contentRef: useCallback((node) => {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
setContentLoaded(true);
|
|
1195
|
-
}
|
|
1254
|
+
contentRef.current = node;
|
|
1255
|
+
setContentLoaded(!!node);
|
|
1196
1256
|
}, []),
|
|
1197
1257
|
active,
|
|
1198
1258
|
align,
|
|
@@ -1219,11 +1279,22 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
|
|
|
1219
1279
|
const IS_BROWSER = typeof window !== "undefined";
|
|
1220
1280
|
const Component$3 = ({ children, trigger, DOMLocation, triggerRefName = "ref", contentRefName = "ref", ...props }) => {
|
|
1221
1281
|
const { active, triggerRef, contentRef, showContent, hideContent, align, alignX, alignY, Provider, ...ctx } = useOverlay(props);
|
|
1222
|
-
const { openOn, closeOn } = props;
|
|
1282
|
+
const { openOn, closeOn, type } = props;
|
|
1283
|
+
const contentId = useId();
|
|
1223
1284
|
const passHandlers = useMemo(() => openOn === "manual" || closeOn === "manual" || closeOn === "clickOutsideContent", [openOn, closeOn]);
|
|
1285
|
+
const ariaHasPopup = useMemo(() => {
|
|
1286
|
+
switch (type) {
|
|
1287
|
+
case "modal": return "dialog";
|
|
1288
|
+
case "tooltip": return "true";
|
|
1289
|
+
default: return "menu";
|
|
1290
|
+
}
|
|
1291
|
+
}, [type]);
|
|
1224
1292
|
return /* @__PURE__ */ jsxs(Fragment, { children: [render(trigger, {
|
|
1225
1293
|
[triggerRefName]: triggerRef,
|
|
1226
1294
|
active,
|
|
1295
|
+
"aria-expanded": active,
|
|
1296
|
+
"aria-haspopup": ariaHasPopup,
|
|
1297
|
+
"aria-controls": active ? contentId : void 0,
|
|
1227
1298
|
...passHandlers ? {
|
|
1228
1299
|
showContent,
|
|
1229
1300
|
hideContent
|
|
@@ -1234,6 +1305,9 @@ const Component$3 = ({ children, trigger, DOMLocation, triggerRefName = "ref", c
|
|
|
1234
1305
|
...ctx,
|
|
1235
1306
|
children: render(children, {
|
|
1236
1307
|
[contentRefName]: contentRef,
|
|
1308
|
+
id: contentId,
|
|
1309
|
+
role: type === "modal" ? "dialog" : void 0,
|
|
1310
|
+
"aria-modal": type === "modal" ? true : void 0,
|
|
1237
1311
|
active,
|
|
1238
1312
|
align,
|
|
1239
1313
|
alignX,
|
|
@@ -1268,9 +1342,11 @@ const styles = ({ css, theme: t }) => css`
|
|
|
1268
1342
|
${t.extraStyles && extendCss(t.extraStyles)};
|
|
1269
1343
|
`;
|
|
1270
1344
|
var styled_default = styled(textComponent)`
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1345
|
+
${css`
|
|
1346
|
+
color: inherit;
|
|
1347
|
+
font-weight: inherit;
|
|
1348
|
+
line-height: 1;
|
|
1349
|
+
`};
|
|
1274
1350
|
|
|
1275
1351
|
${makeItResponsive({
|
|
1276
1352
|
key: "$text",
|
|
@@ -1282,7 +1358,7 @@ var styled_default = styled(textComponent)`
|
|
|
1282
1358
|
|
|
1283
1359
|
//#endregion
|
|
1284
1360
|
//#region src/Text/component.tsx
|
|
1285
|
-
const Component$2 =
|
|
1361
|
+
const Component$2 = ({ paragraph, label, children, tag, css, ref, ...props }) => {
|
|
1286
1362
|
const renderContent = (as = void 0) => /* @__PURE__ */ jsx(styled_default, {
|
|
1287
1363
|
ref,
|
|
1288
1364
|
as,
|
|
@@ -1294,7 +1370,7 @@ const Component$2 = forwardRef(({ paragraph, label, children, tag, css, ...props
|
|
|
1294
1370
|
if (paragraph) finalTag = "p";
|
|
1295
1371
|
else finalTag = tag;
|
|
1296
1372
|
return renderContent(finalTag);
|
|
1297
|
-
}
|
|
1373
|
+
};
|
|
1298
1374
|
const name$1 = `${PKG_NAME}/Text`;
|
|
1299
1375
|
Component$2.displayName = name$1;
|
|
1300
1376
|
Component$2.pkgName = PKG_NAME;
|