@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/README.md +7 -30
- package/lib/index.d.ts +40 -225
- package/lib/index.js +446 -450
- package/lib/vitus-labs-elements.native.js +216 -902
- package/package.json +25 -14
- 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";
|
|
@@ -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$
|
|
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$
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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:
|
|
469
|
+
children: beforeContent
|
|
400
470
|
}),
|
|
401
|
-
isSimpleElement ?
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
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
|
|
637
|
-
|
|
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 =
|
|
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 =
|
|
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/
|
|
723
|
+
//#region src/Overlay/positionMath.ts
|
|
832
724
|
/**
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
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
|
-
|
|
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 [
|
|
1015
|
-
const
|
|
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
|
|
1019
|
-
const
|
|
1020
|
-
const
|
|
1021
|
-
|
|
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 =
|
|
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 (
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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
|
-
|
|
1152
|
-
|
|
1153
|
-
hideContent
|
|
1211
|
+
isContentLoaded,
|
|
1212
|
+
type
|
|
1154
1213
|
]);
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
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
|
-
|
|
1273
|
-
|
|
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
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
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 =
|
|
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
|
|
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
|