@vitus-labs/elements 2.0.0-alpha.9 → 2.0.0-beta.1

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/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, forwardRef, memo, useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
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";
@@ -21,7 +21,7 @@ const IS_DEVELOPMENT = process.env.NODE_ENV !== "production";
21
21
  * equalCols flex distribution. The "content" slot gets `flex: 1` to
22
22
  * fill remaining space between before and after.
23
23
  */
24
- const { styled: styled$2, css: css$2, component: component$2 } = config;
24
+ const { styled: styled$2, css: css$2, component: component$1 } = config;
25
25
  const equalColsCSS = `
26
26
  flex: 1;
27
27
  `;
@@ -67,7 +67,7 @@ const styles$2 = ({ css, theme: t, rootSize }) => css`
67
67
 
68
68
  ${t.extraStyles && extendCss(t.extraStyles)};
69
69
  `;
70
- const StyledComponent = styled$2(component$2)`
70
+ const StyledComponent = styled$2(component$1)`
71
71
  ${`box-sizing: border-box;`};
72
72
 
73
73
  display: flex;
@@ -86,11 +86,22 @@ const StyledComponent = styled$2(component$2)`
86
86
 
87
87
  //#endregion
88
88
  //#region src/helpers/Content/component.tsx
89
- const Component$9 = ({ contentType, tag, parentDirection, direction, alignX, alignY, equalCols, gap, extendCss, ...props }) => {
89
+ /**
90
+ * Memoized content area used inside Element to render one of the three
91
+ * layout slots (before, content, after). Passes alignment, direction,
92
+ * gap, and equalCols styling props to the underlying styled component.
93
+ * Adds a `data-vl-element` attribute in development for debugging.
94
+ *
95
+ * Children are passed as raw content and rendered inside the memo boundary
96
+ * via core `render()` — this lets React.memo skip re-renders when the
97
+ * content reference is stable (common for component-type or string content).
98
+ */
99
+ const Component$9 = ({ contentType, tag, parentDirection, direction, alignX, alignY, equalCols, gap, extendCss, children, ...props }) => {
100
+ const debugProps = IS_DEVELOPMENT ? { "data-vl-element": contentType } : {};
90
101
  return /* @__PURE__ */ jsx(StyledComponent, {
91
102
  as: tag,
92
103
  $contentType: contentType,
93
- $element: {
104
+ $element: useMemo(() => ({
94
105
  contentType,
95
106
  parentDirection,
96
107
  direction,
@@ -99,16 +110,26 @@ const Component$9 = ({ contentType, tag, parentDirection, direction, alignX, ali
99
110
  equalCols,
100
111
  gap,
101
112
  extraStyles: extendCss
102
- },
103
- ...IS_DEVELOPMENT ? { "data-vl-element": contentType } : {},
104
- ...props
113
+ }), [
114
+ contentType,
115
+ parentDirection,
116
+ direction,
117
+ alignX,
118
+ alignY,
119
+ equalCols,
120
+ gap,
121
+ extendCss
122
+ ]),
123
+ ...debugProps,
124
+ ...props,
125
+ children: render(children)
105
126
  });
106
127
  };
107
- var component_default = memo(Component$9);
128
+ var component_default$1 = memo(Component$9);
108
129
 
109
130
  //#endregion
110
131
  //#region src/helpers/Content/index.ts
111
- var Content_default = component_default;
132
+ var Content_default = component_default$1;
112
133
 
113
134
  //#endregion
114
135
  //#region src/helpers/Wrapper/styled.ts
@@ -119,7 +140,7 @@ var Content_default = component_default;
119
140
  * split flex behavior across two DOM nodes for button/fieldset/legend
120
141
  * elements where a single flex container is insufficient.
121
142
  */
122
- const { styled: styled$1, css: css$1, component: component$1 } = config;
143
+ const { styled: styled$1, css: css$1, component } = config;
123
144
  const childFixCSS = `
124
145
  display: flex;
125
146
  flex: 1;
@@ -129,14 +150,12 @@ const childFixCSS = `
129
150
  const parentFixCSS = `
130
151
  flex-direction: column;
131
152
  `;
132
- const parentFixBlockCSS = `
133
- width: 100%;
134
- `;
135
153
  const fullHeightCSS = `
136
154
  height: 100%;
137
155
  `;
138
156
  const blockCSS = `
139
157
  align-self: stretch;
158
+ width: 100%;
140
159
  `;
141
160
  const childFixPosition = (isBlock) => `display: ${isBlock ? "flex" : "inline-flex"};`;
142
161
  const styles$1 = ({ theme: t, css }) => css`
@@ -149,15 +168,15 @@ const styles$1 = ({ theme: t, css }) => css`
149
168
  })};
150
169
 
151
170
  ${t.block && blockCSS};
171
+ ${t.alignY === "block" && t.block && fullHeightCSS};
152
172
 
153
173
  ${!t.childFix && childFixPosition(t.block)};
154
- ${t.parentFix && t.block && parentFixBlockCSS};
155
174
  ${t.parentFix && parentFixCSS};
156
175
 
157
176
  ${t.extraStyles && extendCss(t.extraStyles)};
158
177
  `;
159
178
  const platformCSS = `box-sizing: border-box;`;
160
- var styled_default$1 = styled$1(component$1)`
179
+ var styled_default$1 = styled$1(component)`
161
180
  position: relative;
162
181
  ${platformCSS};
163
182
 
@@ -195,53 +214,68 @@ const isWebFixNeeded = (tag) => {
195
214
  //#region src/helpers/Wrapper/component.tsx
196
215
  /**
197
216
  * Wrapper component that serves as the outermost styled container for Element.
198
- * Uses forwardRef for ref forwarding to the underlying DOM node. On web, it
199
- * 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
200
218
  * (parent + child Styled) because these HTML elements do not natively
201
219
  * support `display: flex` consistently across browsers.
202
220
  */
203
221
  const DEV_PROPS = IS_DEVELOPMENT ? { "data-vl-element": "Element" } : {};
204
- const Component$8 = forwardRef(({ children, tag, block, extendCss, direction, alignX, alignY, equalCols, isInline, ...props }, ref) => {
222
+ const Component$8 = ({ children, ref, tag, block, extendCss, direction, alignX, alignY, equalCols, isInline, ...props }) => {
205
223
  const COMMON_PROPS = {
206
224
  ...props,
207
225
  ...DEV_PROPS,
208
226
  ref,
209
227
  as: tag
210
228
  };
211
- if (!(!props.dangerouslySetInnerHTML && isWebFixNeeded(tag)) || false) return /* @__PURE__ */ jsx(styled_default$1, {
229
+ const needsFix = !props.dangerouslySetInnerHTML && isWebFixNeeded(tag);
230
+ const normalElement = useMemo(() => ({
231
+ block,
232
+ direction,
233
+ alignX,
234
+ alignY,
235
+ equalCols,
236
+ extraStyles: extendCss
237
+ }), [
238
+ block,
239
+ direction,
240
+ alignX,
241
+ alignY,
242
+ equalCols,
243
+ extendCss
244
+ ]);
245
+ const parentFixElement = useMemo(() => ({
246
+ parentFix: true,
247
+ block,
248
+ extraStyles: extendCss
249
+ }), [block, extendCss]);
250
+ const childFixElement = useMemo(() => ({
251
+ childFix: true,
252
+ direction,
253
+ alignX,
254
+ alignY,
255
+ equalCols
256
+ }), [
257
+ direction,
258
+ alignX,
259
+ alignY,
260
+ equalCols
261
+ ]);
262
+ if (!needsFix || false) return /* @__PURE__ */ jsx(styled_default$1, {
212
263
  ...COMMON_PROPS,
213
- $element: {
214
- block,
215
- direction,
216
- alignX,
217
- alignY,
218
- equalCols,
219
- extraStyles: extendCss
220
- },
264
+ $element: normalElement,
221
265
  children
222
266
  });
223
267
  const asTag = isInline ? "span" : "div";
224
268
  return /* @__PURE__ */ jsx(styled_default$1, {
225
269
  ...COMMON_PROPS,
226
- $element: {
227
- parentFix: true,
228
- block,
229
- extraStyles: extendCss
230
- },
270
+ $element: parentFixElement,
231
271
  children: /* @__PURE__ */ jsx(styled_default$1, {
232
272
  as: asTag,
233
273
  $childFix: true,
234
- $element: {
235
- childFix: true,
236
- direction,
237
- alignX,
238
- alignY,
239
- equalCols
240
- },
274
+ $element: childFixElement,
241
275
  children
242
276
  })
243
277
  });
244
- });
278
+ };
245
279
 
246
280
  //#endregion
247
281
  //#region src/helpers/Wrapper/index.ts
@@ -332,11 +366,26 @@ const getShouldBeEmpty = (tag) => {
332
366
  * like void elements (input, img) and inline elements (span, a) by
333
367
  * skipping children or switching sub-tags accordingly.
334
368
  */
369
+ const equalize = (el, direction) => {
370
+ const beforeEl = el.firstElementChild;
371
+ const afterEl = el.lastElementChild;
372
+ if (beforeEl && afterEl && beforeEl !== afterEl) {
373
+ const type = direction === "rows" ? "height" : "width";
374
+ const prop = type === "height" ? "offsetHeight" : "offsetWidth";
375
+ const beforeSize = beforeEl[prop];
376
+ const afterSize = afterEl[prop];
377
+ if (Number.isInteger(beforeSize) && Number.isInteger(afterSize)) {
378
+ const maxSize = `${Math.max(beforeSize, afterSize)}px`;
379
+ beforeEl.style[type] = maxSize;
380
+ afterEl.style[type] = maxSize;
381
+ }
382
+ }
383
+ };
335
384
  const defaultDirection = "inline";
336
385
  const defaultContentDirection = "rows";
337
386
  const defaultAlignX = "left";
338
387
  const defaultAlignY = "center";
339
- const Component$7 = forwardRef(({ innerRef, tag, label, content, children, beforeContent, afterContent, 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 }, ref) => {
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 }) => {
340
389
  const shouldBeEmpty = !!props.dangerouslySetInnerHTML || getShouldBeEmpty(tag);
341
390
  const isSimpleElement = !beforeContent && !afterContent;
342
391
  const CHILDREN = children ?? content ?? label;
@@ -366,8 +415,30 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
366
415
  alignY,
367
416
  direction
368
417
  ]);
418
+ const equalizeRef = useRef(null);
419
+ const externalRef = ref ?? innerRef;
420
+ const mergedRef = useCallback((node) => {
421
+ equalizeRef.current = node;
422
+ if (typeof externalRef === "function") externalRef(node);
423
+ else if (externalRef != null) externalRef.current = node;
424
+ }, [externalRef]);
425
+ useLayoutEffect(() => {
426
+ if (!equalBeforeAfter || !beforeContent || !afterContent) return;
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();
434
+ }, [
435
+ equalBeforeAfter,
436
+ beforeContent,
437
+ afterContent,
438
+ direction
439
+ ]);
369
440
  const WRAPPER_PROPS = {
370
- ref: ref ?? innerRef,
441
+ ref: mergedRef,
371
442
  extendCss: css,
372
443
  tag,
373
444
  block,
@@ -380,7 +451,6 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
380
451
  ...props,
381
452
  ...WRAPPER_PROPS
382
453
  });
383
- const contentRenderOutput = render(CHILDREN);
384
454
  return /* @__PURE__ */ jsxs(Wrapper_default, {
385
455
  ...props,
386
456
  ...WRAPPER_PROPS,
@@ -396,9 +466,9 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
396
466
  alignY: beforeContentAlignY,
397
467
  equalCols,
398
468
  gap,
399
- children: render(beforeContent)
469
+ children: beforeContent
400
470
  }),
401
- isSimpleElement ? contentRenderOutput : /* @__PURE__ */ jsx(Content_default, {
471
+ isSimpleElement ? render(CHILDREN) : /* @__PURE__ */ jsx(Content_default, {
402
472
  tag: SUB_TAG,
403
473
  contentType: "content",
404
474
  parentDirection: wrapperDirection,
@@ -407,7 +477,7 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
407
477
  alignX: contentAlignX,
408
478
  alignY: contentAlignY,
409
479
  equalCols,
410
- children: contentRenderOutput
480
+ children: CHILDREN
411
481
  }),
412
482
  afterContent && /* @__PURE__ */ jsx(Content_default, {
413
483
  tag: SUB_TAG,
@@ -419,63 +489,16 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
419
489
  alignY: afterContentAlignY,
420
490
  equalCols,
421
491
  gap,
422
- children: render(afterContent)
492
+ children: afterContent
423
493
  })
424
494
  ]
425
495
  });
426
- });
496
+ };
427
497
  const name$5 = `${PKG_NAME}/Element`;
428
498
  Component$7.displayName = name$5;
429
499
  Component$7.pkgName = PKG_NAME;
430
500
  Component$7.VITUS_LABS__COMPONENT = name$5;
431
501
 
432
- //#endregion
433
- //#region src/Element/withEqualSizeBeforeAfter.tsx
434
- /**
435
- * HOC that equalizes the dimensions of beforeContent and afterContent areas.
436
- * After render, it measures both DOM nodes via useLayoutEffect and sets the
437
- * larger dimension on both so they match. Uses width for inline direction
438
- * and height for rows direction. This is useful for centering the main
439
- * content when before/after slots have different intrinsic sizes.
440
- */
441
- const types = {
442
- height: "offsetHeight",
443
- width: "offsetWidth"
444
- };
445
- const equalize = (beforeEl, afterEl, type) => {
446
- const prop = types[type];
447
- const beforeSize = beforeEl[prop];
448
- const afterSize = afterEl[prop];
449
- if (Number.isInteger(beforeSize) && Number.isInteger(afterSize)) {
450
- const maxSize = `${Math.max(beforeSize, afterSize)}px`;
451
- beforeEl.style[type] = maxSize;
452
- afterEl.style[type] = maxSize;
453
- }
454
- };
455
- const withEqualBeforeAfter = (WrappedComponent) => {
456
- const displayName = WrappedComponent.displayName ?? WrappedComponent.name ?? "Component";
457
- const Enhanced = ({ equalBeforeAfter, direction, afterContent, beforeContent, ref, ...rest }) => {
458
- const internalRef = useRef(null);
459
- useImperativeHandle(ref, () => internalRef.current);
460
- useLayoutEffect(() => {
461
- if (!equalBeforeAfter || !beforeContent || !afterContent) return;
462
- if (!internalRef.current) return;
463
- const el = internalRef.current;
464
- const beforeEl = el.firstElementChild;
465
- const afterEl = el.lastElementChild;
466
- if (beforeEl && afterEl && beforeEl !== afterEl) equalize(beforeEl, afterEl, direction === "rows" ? "height" : "width");
467
- });
468
- return /* @__PURE__ */ jsx(WrappedComponent, {
469
- ...rest,
470
- afterContent,
471
- beforeContent,
472
- ref: internalRef
473
- });
474
- };
475
- Enhanced.displayName = `withEqualSizeBeforeAfter(${displayName})`;
476
- return Enhanced;
477
- };
478
-
479
502
  //#endregion
480
503
  //#region src/Element/index.ts
481
504
  var Element_default = Component$7;
@@ -490,27 +513,6 @@ var Element_default = Component$7;
490
513
  * wrapped with `wrapComponent`. Children always take priority over the
491
514
  * component+data prop pattern.
492
515
  */
493
- const classifyData = (data) => {
494
- const items = data.filter((item) => item != null && !(typeof item === "object" && isEmpty(item)));
495
- if (items.length === 0) return null;
496
- let isSimple = true;
497
- let isComplex = true;
498
- for (const item of items) if (typeof item === "string" || typeof item === "number") isComplex = false;
499
- else if (typeof item === "object") isSimple = false;
500
- else {
501
- isSimple = false;
502
- isComplex = false;
503
- }
504
- if (isSimple) return {
505
- type: "simple",
506
- data: items
507
- };
508
- if (isComplex) return {
509
- type: "complex",
510
- data: items
511
- };
512
- return null;
513
- };
514
516
  const RESERVED_PROPS = [
515
517
  "children",
516
518
  "component",
@@ -521,7 +523,7 @@ const RESERVED_PROPS = [
521
523
  "itemProps",
522
524
  "wrapProps"
523
525
  ];
524
- const attachItemProps = ({ i, length }) => {
526
+ const buildExtendedProps = (i, length) => {
525
527
  const position = i + 1;
526
528
  return {
527
529
  index: i,
@@ -532,113 +534,105 @@ const attachItemProps = ({ i, length }) => {
532
534
  position
533
535
  };
534
536
  };
535
- const Component$6 = (props) => {
536
- const { itemKey, valueName, children, component, data, wrapComponent: Wrapper, wrapProps, itemProps } = props;
537
- const injectItemProps = useMemo(() => typeof itemProps === "function" ? itemProps : () => itemProps, [itemProps]);
538
- const injectWrapItemProps = useMemo(() => typeof wrapProps === "function" ? wrapProps : () => wrapProps, [wrapProps]);
539
- const getKey = useCallback((item, index) => {
540
- if (typeof itemKey === "function") return itemKey(item, index);
541
- return index;
542
- }, [itemKey]);
543
- const renderChild = (child, total = 1, i = 0) => {
544
- if (!itemProps && !Wrapper) return child;
545
- const extendedProps = attachItemProps({
546
- i,
547
- length: total
548
- });
549
- const finalItemProps = itemProps ? injectItemProps({}, extendedProps) : {};
550
- if (Wrapper) return /* @__PURE__ */ jsx(Wrapper, {
551
- ...wrapProps ? injectWrapItemProps({}, extendedProps) : {},
552
- children: render(child, finalItemProps)
553
- }, i);
554
- return render(child, {
555
- key: i,
556
- ...finalItemProps
557
- });
558
- };
559
- const renderChildren = () => {
560
- if (!children) return null;
561
- if (Array.isArray(children)) return Children.map(children, (item, i) => renderChild(item, children.length, i));
562
- if (isFragment(children)) {
563
- const fragmentChildren = children.props.children;
564
- const childrenLength = fragmentChildren.length;
565
- return fragmentChildren.map((item, i) => renderChild(item, childrenLength, i));
566
- }
567
- return renderChild(children);
568
- };
569
- const renderSimpleArray = (data) => {
570
- const { length } = data;
571
- if (length === 0) return null;
572
- return data.map((item, i) => {
573
- const key = getKey(item, i);
574
- const keyName = valueName ?? "children";
575
- const extendedProps = attachItemProps({
576
- i,
577
- length
578
- });
579
- const finalItemProps = {
580
- ...itemProps ? injectItemProps({ [keyName]: item }, extendedProps) : {},
581
- [keyName]: item
582
- };
583
- if (Wrapper) return /* @__PURE__ */ jsx(Wrapper, {
584
- ...wrapProps ? injectWrapItemProps({ [keyName]: item }, extendedProps) : {},
585
- children: render(component, finalItemProps)
586
- }, key);
587
- return render(component, {
588
- key,
589
- ...finalItemProps
590
- });
591
- });
592
- };
593
- const getObjectKey = (item, index) => {
594
- if (!itemKey) return item.key ?? item.id ?? item.itemId ?? index;
595
- if (typeof itemKey === "function") return itemKey(item, index);
596
- if (typeof itemKey === "string") return item[itemKey];
597
- 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
598
545
  };
599
- const renderComplexArray = (data) => {
600
- const { length } = data;
601
- if (length === 0) return null;
602
- return data.map((item, i) => {
603
- const { component: itemComponent, ...restItem } = item;
604
- const renderItem = itemComponent ?? component;
605
- const key = getObjectKey(restItem, i);
606
- const extendedProps = attachItemProps({
607
- i,
608
- length
609
- });
610
- const finalItemProps = {
611
- ...itemProps ? injectItemProps(item, extendedProps) : {},
612
- ...restItem
613
- };
614
- if (Wrapper && !itemComponent) return /* @__PURE__ */ jsx(Wrapper, {
615
- ...wrapProps ? injectWrapItemProps(item, extendedProps) : {},
616
- children: render(renderItem, finalItemProps)
617
- }, key);
618
- return render(renderItem, {
619
- key,
620
- ...finalItemProps
621
- });
622
- });
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
623
553
  };
624
- const renderItems = () => {
625
- if (children) return renderChildren();
626
- if (component && Array.isArray(data)) {
627
- const classified = classifyData(data);
628
- if (!classified) return null;
629
- if (classified.type === "simple") return renderSimpleArray(classified.data);
630
- return renderComplexArray(classified.data);
631
- }
632
- return null;
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)
633
609
  };
634
- return renderItems();
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)));
635
627
  };
636
- Component$6.isIterator = true;
637
- Component$6.RESERVED_PROPS = RESERVED_PROPS;
628
+ var component_default = Object.assign(memo(Component$6), {
629
+ isIterator: true,
630
+ RESERVED_PROPS
631
+ });
638
632
 
639
633
  //#endregion
640
634
  //#region src/helpers/Iterator/index.ts
641
- var Iterator_default = Component$6;
635
+ var Iterator_default = component_default;
642
636
 
643
637
  //#endregion
644
638
  //#region src/List/component.tsx
@@ -649,7 +643,7 @@ var Iterator_default = Component$6;
649
643
  * is wrapped in an Element that receives all non-iterator props (e.g.,
650
644
  * layout, alignment, css), allowing the list to be styled as a single block.
651
645
  */
652
- const Component$5 = forwardRef(({ rootElement = false, ...props }, ref) => {
646
+ const Component$5 = ({ rootElement = false, ref, ...props }) => {
653
647
  const renderedList = /* @__PURE__ */ jsx(Iterator_default, { ...pick(props, Iterator_default.RESERVED_PROPS) });
654
648
  if (!rootElement) return renderedList;
655
649
  return /* @__PURE__ */ jsx(Element_default, {
@@ -657,114 +651,12 @@ const Component$5 = forwardRef(({ rootElement = false, ...props }, ref) => {
657
651
  ...omit(props, Iterator_default.RESERVED_PROPS),
658
652
  children: renderedList
659
653
  });
660
- });
654
+ };
661
655
  const name$4 = `${PKG_NAME}/List`;
662
656
  Component$5.displayName = name$4;
663
657
  Component$5.pkgName = PKG_NAME;
664
658
  Component$5.VITUS_LABS__COMPONENT = name$4;
665
659
 
666
- //#endregion
667
- //#region src/List/withActiveState.tsx
668
- /**
669
- * HOC that adds single or multi selection state management to a list component.
670
- * Tracks which items are active via a scalar key (single mode) or a Map of
671
- * key-to-boolean entries (multi mode). Injects `itemProps` callback that
672
- * provides each item with `active`, `handleItemActive`, `toggleItemActive`,
673
- * and other selection helpers. Supports `activeItemRequired` to prevent
674
- * deselecting the last active item.
675
- */
676
- const RESERVED_KEYS = [
677
- "type",
678
- "activeItems",
679
- "itemProps",
680
- "activeItemRequired"
681
- ];
682
- const component = (WrappedComponent) => {
683
- const displayName = WrappedComponent.displayName || WrappedComponent.name || "Component";
684
- const Enhanced = (props) => {
685
- const { type = "single", activeItemRequired, activeItems, itemProps = {}, ...rest } = props;
686
- const initActiveItems = () => {
687
- if (type === "single") {
688
- if (!Array.isArray(activeItems)) return activeItems;
689
- } else if (type === "multi") {
690
- const activeItemsHelper = Array.isArray(activeItems) ? activeItems : [activeItems];
691
- return new Map(activeItemsHelper.map((id) => [id, true]));
692
- }
693
- };
694
- const [innerActiveItems, setActiveItems] = useState(initActiveItems());
695
- const countActiveItems = (data) => {
696
- let result = 0;
697
- data.forEach((value) => {
698
- if (value) result += 1;
699
- });
700
- return result;
701
- };
702
- const updateItemState = (key) => {
703
- if (type === "single") setActiveItems((prevState) => {
704
- if (activeItemRequired) return key;
705
- if (prevState === key) return void 0;
706
- return key;
707
- });
708
- else if (type === "multi") setActiveItems((prevState) => {
709
- const activeItems = new Map(prevState);
710
- if (activeItemRequired && activeItems.get(key) && countActiveItems(activeItems) === 1) return activeItems;
711
- activeItems.set(key, !activeItems.get(key));
712
- return activeItems;
713
- });
714
- else setActiveItems(void 0);
715
- };
716
- const handleItemActive = (key) => {
717
- updateItemState(key);
718
- };
719
- const updateAllItemsState = (status) => {
720
- if (!status) setActiveItems(/* @__PURE__ */ new Map());
721
- };
722
- const setItemActive = (key) => {
723
- updateItemState(key);
724
- };
725
- const unsetItemActive = (key) => {
726
- updateItemState(key);
727
- };
728
- const toggleItemActive = (key) => {
729
- updateItemState(key);
730
- };
731
- const unsetAllItemsActive = () => {
732
- updateAllItemsState(false);
733
- };
734
- const isItemActive = (key) => {
735
- if (!innerActiveItems) return false;
736
- if (type === "single") return innerActiveItems === key;
737
- if (type === "multi" && innerActiveItems instanceof Map) return !!innerActiveItems.get(key);
738
- return false;
739
- };
740
- const attachMultipleProps = { unsetAllItemsActive };
741
- const attachItemProps = (props) => {
742
- const { key } = props;
743
- return {
744
- ...typeof itemProps === "object" ? itemProps : itemProps(props),
745
- active: isItemActive(key),
746
- handleItemActive: () => handleItemActive(key),
747
- setItemActive,
748
- unsetItemActive,
749
- toggleItemActive,
750
- ...type === "multi" ? attachMultipleProps : {}
751
- };
752
- };
753
- useEffect(() => {
754
- if (type === "single" && Array.isArray(activeItems)) {
755
- if (process.env.NODE_ENV !== "production") console.warn("[@vitus-labs/elements] List/withActiveState: `activeItems` was passed as an array but `type` is \"single\". In single selection mode, `activeItems` should be a single key (string | number). The array value will be ignored.");
756
- }
757
- }, [type, activeItems]);
758
- return /* @__PURE__ */ jsx(WrappedComponent, {
759
- ...rest,
760
- itemProps: attachItemProps
761
- });
762
- };
763
- Enhanced.RESERVED_KEYS = RESERVED_KEYS;
764
- Enhanced.displayName = `@vitus-labs/elements/List/withActiveState(${displayName})`;
765
- return Enhanced;
766
- };
767
-
768
660
  //#endregion
769
661
  //#region src/List/index.ts
770
662
  var List_default = Component$5;
@@ -828,14 +720,12 @@ const Component = ({ children, blocked, setBlocked, setUnblocked }) => {
828
720
  };
829
721
 
830
722
  //#endregion
831
- //#region src/Overlay/useOverlay.tsx
723
+ //#region src/Overlay/positionMath.ts
832
724
  /**
833
- * Core hook powering the Overlay component. Manages open/close state, DOM
834
- * event listeners (click, hover, scroll, resize, ESC key), and dynamic
835
- * positioning of overlay content relative to its trigger. Supports dropdown,
836
- * tooltip, popover, and modal types with automatic edge-of-viewport flipping.
837
- * Event handlers are throttled for performance, and nested overlay blocking
838
- * 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).
839
729
  */
840
730
  const sel = (cond, a, b) => cond ? a : b;
841
731
  const devWarn = (msg) => {
@@ -1005,24 +895,192 @@ const processVisibilityEvent = (e, active, openOn, closeOn, isTrigger, isContent
1005
895
  else if (closeOn === "clickOnTrigger" && isTrigger(e)) hideContent();
1006
896
  else if (closeOn === "clickOutsideContent" && !isContent(e)) hideContent();
1007
897
  };
1008
- 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, disabled, onOpen, onClose } = {}) => {
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 } = {}) => {
1009
1069
  const { rootSize } = useContext(context);
1010
1070
  const ctx = useOverlayContext();
1011
1071
  const [isContentLoaded, setContentLoaded] = useState(false);
1012
1072
  const [innerAlignX, setInnerAlignX] = useState(alignX);
1013
1073
  const [innerAlignY, setInnerAlignY] = useState(alignY);
1014
- const [blocked, handleBlocked] = useState(false);
1015
- const [active, handleActive] = useState(isOpen);
1074
+ const [blockedCount, setBlockedCount] = useState(0);
1075
+ const blocked = blockedCount > 0;
1076
+ const [active, setActive] = useState(isOpen);
1016
1077
  const triggerRef = useRef(null);
1017
1078
  const contentRef = useRef(null);
1018
- const setBlocked = useCallback(() => handleBlocked(true), []);
1019
- const setUnblocked = useCallback(() => handleBlocked(false), []);
1020
- const showContent = useCallback(() => {
1021
- handleActive(true);
1022
- }, []);
1023
- const hideContent = useCallback(() => {
1024
- handleActive(false);
1025
- }, []);
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), []);
1026
1084
  const getAncestorOffset = useCallback(() => {
1027
1085
  if (position !== "absolute" || !contentRef.current) return {
1028
1086
  top: 0,
@@ -1091,7 +1149,7 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
1091
1149
  const latestHandleVisibility = useRef(handleVisibilityByEventType);
1092
1150
  latestHandleVisibility.current = handleVisibilityByEventType;
1093
1151
  const handleContentPosition = useMemo(() => throttle(() => latestSetContentPosition.current(), throttleDelay), [throttleDelay]);
1094
- const handleClick = handleVisibilityByEventType;
1152
+ const handleClick = useCallback((e) => latestHandleVisibility.current(e), []);
1095
1153
  const handleVisibility = useMemo(() => throttle((e) => latestHandleVisibility.current(e), throttleDelay), [throttleDelay]);
1096
1154
  useEffect(() => {
1097
1155
  setInnerAlignX(alignX);
@@ -1138,60 +1196,30 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
1138
1196
  onOpen
1139
1197
  ]);
1140
1198
  useEffect(() => {
1141
- if (!closeOnEsc || !active || blocked) return void 0;
1142
- const handleEscKey = (e) => {
1143
- if (e.key === "Escape") hideContent();
1144
- };
1145
- window.addEventListener("keydown", handleEscKey);
1146
- return () => {
1147
- window.removeEventListener("keydown", handleEscKey);
1148
- };
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
+ }
1149
1209
  }, [
1150
1210
  active,
1151
- blocked,
1152
- closeOnEsc,
1153
- hideContent
1211
+ isContentLoaded,
1212
+ type
1154
1213
  ]);
1155
- useEffect(() => {
1156
- if (!active) return void 0;
1157
- const shouldSetOverflow = type === "modal";
1158
- const onScroll = (e) => {
1159
- handleContentPosition();
1160
- handleVisibility(e);
1161
- };
1162
- if (shouldSetOverflow) document.body.style.overflow = "hidden";
1163
- window.addEventListener("resize", handleContentPosition);
1164
- window.addEventListener("scroll", onScroll, { passive: true });
1165
- return () => {
1166
- if (shouldSetOverflow) document.body.style.overflow = "";
1167
- window.removeEventListener("resize", handleContentPosition);
1168
- window.removeEventListener("scroll", onScroll);
1169
- };
1170
- }, [
1214
+ useEscapeKey(closeOnEsc, active, blocked, hideContent);
1215
+ useScrollReposition({
1171
1216
  active,
1172
1217
  type,
1173
- handleVisibility,
1174
- handleContentPosition
1175
- ]);
1176
- useEffect(() => {
1177
- if (!active || !parentContainer) return void 0;
1178
- if (closeOn !== "hover") parentContainer.style.overflow = "hidden";
1179
- const onScroll = (e) => {
1180
- handleContentPosition();
1181
- handleVisibility(e);
1182
- };
1183
- parentContainer.addEventListener("scroll", onScroll, { passive: true });
1184
- return () => {
1185
- parentContainer.style.overflow = "";
1186
- parentContainer.removeEventListener("scroll", onScroll);
1187
- };
1188
- }, [
1189
- active,
1190
1218
  parentContainer,
1191
1219
  closeOn,
1192
1220
  handleContentPosition,
1193
1221
  handleVisibility
1194
- ]);
1222
+ });
1195
1223
  useEffect(() => {
1196
1224
  if (blocked || disabled) return void 0;
1197
1225
  if (openOn === "click" || [
@@ -1199,9 +1227,7 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
1199
1227
  "clickOnTrigger",
1200
1228
  "clickOutsideContent"
1201
1229
  ].includes(closeOn)) window.addEventListener("click", handleClick);
1202
- return () => {
1203
- window.removeEventListener("click", handleClick);
1204
- };
1230
+ return () => window.removeEventListener("click", handleClick);
1205
1231
  }, [
1206
1232
  openOn,
1207
1233
  closeOn,
@@ -1209,70 +1235,24 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
1209
1235
  disabled,
1210
1236
  handleClick
1211
1237
  ]);
1212
- const hoverTimeoutRef = useRef(null);
1213
- useEffect(() => {
1214
- if (blocked || disabled || !(openOn === "hover" || closeOn === "hover")) return void 0;
1215
- const trigger = triggerRef.current;
1216
- const content = contentRef.current;
1217
- const clearHoverTimeout = () => {
1218
- if (hoverTimeoutRef.current != null) {
1219
- clearTimeout(hoverTimeoutRef.current);
1220
- hoverTimeoutRef.current = null;
1221
- }
1222
- };
1223
- const scheduleHide = () => {
1224
- clearHoverTimeout();
1225
- hoverTimeoutRef.current = setTimeout(hideContent, 100);
1226
- };
1227
- const onTriggerEnter = () => {
1228
- clearHoverTimeout();
1229
- if (openOn === "hover" && !active) showContent();
1230
- };
1231
- const onTriggerLeave = () => {
1232
- if (closeOn === "hover" && active) scheduleHide();
1233
- };
1234
- const onContentEnter = () => {
1235
- clearHoverTimeout();
1236
- };
1237
- const onContentLeave = () => {
1238
- if (closeOn === "hover" && active) scheduleHide();
1239
- };
1240
- if (trigger) {
1241
- trigger.addEventListener("mouseenter", onTriggerEnter);
1242
- trigger.addEventListener("mouseleave", onTriggerLeave);
1243
- }
1244
- if (content) {
1245
- content.addEventListener("mouseenter", onContentEnter);
1246
- content.addEventListener("mouseleave", onContentLeave);
1247
- }
1248
- return () => {
1249
- clearHoverTimeout();
1250
- if (trigger) {
1251
- trigger.removeEventListener("mouseenter", onTriggerEnter);
1252
- trigger.removeEventListener("mouseleave", onTriggerLeave);
1253
- }
1254
- if (content) {
1255
- content.removeEventListener("mouseenter", onContentEnter);
1256
- content.removeEventListener("mouseleave", onContentLeave);
1257
- }
1258
- };
1259
- }, [
1260
- active,
1238
+ useHoverListeners({
1239
+ triggerRef,
1240
+ contentRef,
1261
1241
  isContentLoaded,
1242
+ active,
1262
1243
  blocked,
1263
1244
  disabled,
1264
1245
  openOn,
1265
1246
  closeOn,
1247
+ hoverDelay,
1266
1248
  showContent,
1267
1249
  hideContent
1268
- ]);
1250
+ });
1269
1251
  return {
1270
1252
  triggerRef,
1271
1253
  contentRef: useCallback((node) => {
1272
- if (node) {
1273
- contentRef.current = node;
1274
- setContentLoaded(true);
1275
- }
1254
+ contentRef.current = node;
1255
+ setContentLoaded(!!node);
1276
1256
  }, []),
1277
1257
  active,
1278
1258
  align,
@@ -1299,11 +1279,22 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
1299
1279
  const IS_BROWSER = typeof window !== "undefined";
1300
1280
  const Component$3 = ({ children, trigger, DOMLocation, triggerRefName = "ref", contentRefName = "ref", ...props }) => {
1301
1281
  const { active, triggerRef, contentRef, showContent, hideContent, align, alignX, alignY, Provider, ...ctx } = useOverlay(props);
1302
- const { openOn, closeOn } = props;
1282
+ const { openOn, closeOn, type } = props;
1283
+ const contentId = useId();
1303
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]);
1304
1292
  return /* @__PURE__ */ jsxs(Fragment, { children: [render(trigger, {
1305
1293
  [triggerRefName]: triggerRef,
1306
1294
  active,
1295
+ "aria-expanded": active,
1296
+ "aria-haspopup": ariaHasPopup,
1297
+ "aria-controls": active ? contentId : void 0,
1307
1298
  ...passHandlers ? {
1308
1299
  showContent,
1309
1300
  hideContent
@@ -1314,6 +1305,9 @@ const Component$3 = ({ children, trigger, DOMLocation, triggerRefName = "ref", c
1314
1305
  ...ctx,
1315
1306
  children: render(children, {
1316
1307
  [contentRefName]: contentRef,
1308
+ id: contentId,
1309
+ role: type === "modal" ? "dialog" : void 0,
1310
+ "aria-modal": type === "modal" ? true : void 0,
1317
1311
  active,
1318
1312
  align,
1319
1313
  alignX,
@@ -1348,9 +1342,11 @@ const styles = ({ css, theme: t }) => css`
1348
1342
  ${t.extraStyles && extendCss(t.extraStyles)};
1349
1343
  `;
1350
1344
  var styled_default = styled(textComponent)`
1351
- color: inherit;
1352
- font-weight: inherit;
1353
- line-height: 1;
1345
+ ${css`
1346
+ color: inherit;
1347
+ font-weight: inherit;
1348
+ line-height: 1;
1349
+ `};
1354
1350
 
1355
1351
  ${makeItResponsive({
1356
1352
  key: "$text",
@@ -1362,7 +1358,7 @@ var styled_default = styled(textComponent)`
1362
1358
 
1363
1359
  //#endregion
1364
1360
  //#region src/Text/component.tsx
1365
- const Component$2 = forwardRef(({ paragraph, label, children, tag, css, ...props }, ref) => {
1361
+ const Component$2 = ({ paragraph, label, children, tag, css, ref, ...props }) => {
1366
1362
  const renderContent = (as = void 0) => /* @__PURE__ */ jsx(styled_default, {
1367
1363
  ref,
1368
1364
  as,
@@ -1374,7 +1370,7 @@ const Component$2 = forwardRef(({ paragraph, label, children, tag, css, ...props
1374
1370
  if (paragraph) finalTag = "p";
1375
1371
  else finalTag = tag;
1376
1372
  return renderContent(finalTag);
1377
- });
1373
+ };
1378
1374
  const name$1 = `${PKG_NAME}/Text`;
1379
1375
  Component$2.displayName = name$1;
1380
1376
  Component$2.pkgName = PKG_NAME;
@@ -1409,5 +1405,5 @@ Component$1.VITUS_LABS__COMPONENT = name;
1409
1405
  var Util_default = Component$1;
1410
1406
 
1411
1407
  //#endregion
1412
- export { Element_default as Element, List_default as List, Overlay_default as Overlay, Component as OverlayProvider, Portal_default as Portal, Provider, Text_default as Text, Util_default as Util, useOverlay, component as withActiveState, withEqualBeforeAfter as withEqualSizeBeforeAfter };
1408
+ export { Element_default as Element, List_default as List, Overlay_default as Overlay, Component as OverlayProvider, Portal_default as Portal, Provider, Text_default as Text, Util_default as Util, useOverlay };
1413
1409
  //# sourceMappingURL=index.js.map