@vitus-labs/elements 1.2.1 → 1.2.3-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +279 -66
  3. package/lib/analysis/index.js.html +18 -18
  4. package/lib/analysis/vitus-labs-elements.native.js.html +18 -18
  5. package/lib/index.d.ts +832 -850
  6. package/lib/index.d.ts.map +1 -0
  7. package/lib/index.js +1290 -1160
  8. package/lib/index.js.map +1 -1
  9. package/lib/vitus-labs-elements.native.js +1176 -1060
  10. package/lib/vitus-labs-elements.native.js.map +1 -1
  11. package/package.json +24 -24
  12. package/lib/types/Element/component.d.ts +0 -4
  13. package/lib/types/Element/component.d.ts.map +0 -1
  14. package/lib/types/Element/constants.d.ts +0 -51
  15. package/lib/types/Element/constants.d.ts.map +0 -1
  16. package/lib/types/Element/index.d.ts +0 -7
  17. package/lib/types/Element/index.d.ts.map +0 -1
  18. package/lib/types/Element/types.d.ts +0 -423
  19. package/lib/types/Element/types.d.ts.map +0 -1
  20. package/lib/types/Element/utils.d.ts +0 -5
  21. package/lib/types/Element/utils.d.ts.map +0 -1
  22. package/lib/types/Element/withEqualSizeBeforeAfter.d.ts +0 -8
  23. package/lib/types/Element/withEqualSizeBeforeAfter.d.ts.map +0 -1
  24. package/lib/types/List/component.d.ts +0 -23
  25. package/lib/types/List/component.d.ts.map +0 -1
  26. package/lib/types/List/index.d.ts +0 -7
  27. package/lib/types/List/index.d.ts.map +0 -1
  28. package/lib/types/List/withActiveState.d.ts +0 -11
  29. package/lib/types/List/withActiveState.d.ts.map +0 -1
  30. package/lib/types/Overlay/component.d.ts +0 -52
  31. package/lib/types/Overlay/component.d.ts.map +0 -1
  32. package/lib/types/Overlay/context.d.ts +0 -12
  33. package/lib/types/Overlay/context.d.ts.map +0 -1
  34. package/lib/types/Overlay/index.d.ts +0 -7
  35. package/lib/types/Overlay/index.d.ts.map +0 -1
  36. package/lib/types/Overlay/useOverlay.d.ts +0 -109
  37. package/lib/types/Overlay/useOverlay.d.ts.map +0 -1
  38. package/lib/types/Portal/component.d.ts +0 -20
  39. package/lib/types/Portal/component.d.ts.map +0 -1
  40. package/lib/types/Portal/index.d.ts +0 -5
  41. package/lib/types/Portal/index.d.ts.map +0 -1
  42. package/lib/types/Text/component.d.ts +0 -30
  43. package/lib/types/Text/component.d.ts.map +0 -1
  44. package/lib/types/Text/index.d.ts +0 -5
  45. package/lib/types/Text/index.d.ts.map +0 -1
  46. package/lib/types/Text/styled.d.ts +0 -3
  47. package/lib/types/Text/styled.d.ts.map +0 -1
  48. package/lib/types/Util/component.d.ts +0 -19
  49. package/lib/types/Util/component.d.ts.map +0 -1
  50. package/lib/types/Util/index.d.ts +0 -5
  51. package/lib/types/Util/index.d.ts.map +0 -1
  52. package/lib/types/constants.d.ts +0 -2
  53. package/lib/types/constants.d.ts.map +0 -1
  54. package/lib/types/helpers/Content/component.d.ts +0 -5
  55. package/lib/types/helpers/Content/component.d.ts.map +0 -1
  56. package/lib/types/helpers/Content/index.d.ts +0 -3
  57. package/lib/types/helpers/Content/index.d.ts.map +0 -1
  58. package/lib/types/helpers/Content/styled.d.ts +0 -3
  59. package/lib/types/helpers/Content/styled.d.ts.map +0 -1
  60. package/lib/types/helpers/Content/types.d.ts +0 -31
  61. package/lib/types/helpers/Content/types.d.ts.map +0 -1
  62. package/lib/types/helpers/Iterator/component.d.ts +0 -10
  63. package/lib/types/helpers/Iterator/component.d.ts.map +0 -1
  64. package/lib/types/helpers/Iterator/index.d.ts +0 -5
  65. package/lib/types/helpers/Iterator/index.d.ts.map +0 -1
  66. package/lib/types/helpers/Iterator/types.d.ts +0 -60
  67. package/lib/types/helpers/Iterator/types.d.ts.map +0 -1
  68. package/lib/types/helpers/Wrapper/component.d.ts +0 -5
  69. package/lib/types/helpers/Wrapper/component.d.ts.map +0 -1
  70. package/lib/types/helpers/Wrapper/constants.d.ts +0 -6
  71. package/lib/types/helpers/Wrapper/constants.d.ts.map +0 -1
  72. package/lib/types/helpers/Wrapper/index.d.ts +0 -3
  73. package/lib/types/helpers/Wrapper/index.d.ts.map +0 -1
  74. package/lib/types/helpers/Wrapper/styled.d.ts +0 -3
  75. package/lib/types/helpers/Wrapper/styled.d.ts.map +0 -1
  76. package/lib/types/helpers/Wrapper/types.d.ts +0 -42
  77. package/lib/types/helpers/Wrapper/types.d.ts.map +0 -1
  78. package/lib/types/helpers/Wrapper/utils.d.ts +0 -4
  79. package/lib/types/helpers/Wrapper/utils.d.ts.map +0 -1
  80. package/lib/types/helpers/index.d.ts +0 -4
  81. package/lib/types/helpers/index.d.ts.map +0 -1
  82. package/lib/types/index.d.ts +0 -18
  83. package/lib/types/index.d.ts.map +0 -1
  84. package/lib/types/types.d.ts +0 -51
  85. package/lib/types/types.d.ts.map +0 -1
  86. package/lib/types/utils.d.ts +0 -2
  87. package/lib/types/utils.d.ts.map +0 -1
package/lib/index.js CHANGED
@@ -1,30 +1,125 @@
1
- import { makeItResponsive, alignContent, extendCss, value } from '@vitus-labs/unistyle';
2
- export { Provider } from '@vitus-labs/unistyle';
3
- import React, { forwardRef, memo, useMemo, createRef, useCallback, Children, useState, useEffect, createContext, useContext, useRef } from 'react';
4
- import { config, render, get, isEmpty, pick, omit, context as context$1, throttle } from '@vitus-labs/core';
5
- import { isFragment } from 'react-is';
6
- import { createPortal } from 'react-dom';
1
+ import { Provider, alignContent, extendCss, makeItResponsive, value } from "@vitus-labs/unistyle";
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";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { isFragment } from "react-is";
6
+ import { createPortal } from "react-dom";
7
7
 
8
- const PKG_NAME = '@vitus-labs/elements';
8
+ //#region src/constants.ts
9
+ const PKG_NAME = "@vitus-labs/elements";
9
10
 
10
- const IS_DEVELOPMENT = process.env.NODE_ENV !== 'production';
11
+ //#endregion
12
+ //#region src/utils.ts
13
+ const IS_DEVELOPMENT = process.env.NODE_ENV !== "production";
11
14
 
12
- /* eslint-disable import/prefer-default-export */
13
- // https://stackoverflow.com/questions/35464067/flexbox-not-working-on-button-or-fieldset-elements
14
- const INLINE_ELEMENTS_FLEX_FIX = {
15
- button: true,
16
- fieldset: true,
17
- legend: true,
15
+ //#endregion
16
+ //#region src/helpers/Content/styled.ts
17
+ /**
18
+ * Styled component for content areas (before/content/after). Applies
19
+ * responsive flex alignment, gap spacing between slots based on parent
20
+ * direction (margin-right for inline, margin-bottom for rows), and
21
+ * equalCols flex distribution. The "content" slot gets `flex: 1` to
22
+ * fill remaining space between before and after.
23
+ */
24
+ const { styled: styled$2, css: css$2, component: component$2 } = config;
25
+ const equalColsCSS = `
26
+ flex: 1;
27
+ `;
28
+ const typeContentCSS = `
29
+ flex: 1;
30
+ `;
31
+ const gapDimensions = {
32
+ inline: {
33
+ before: "margin-right",
34
+ after: "margin-left"
35
+ },
36
+ reverseInline: {
37
+ before: "margin-right",
38
+ after: "margin-left"
39
+ },
40
+ rows: {
41
+ before: "margin-bottom",
42
+ after: "margin-top"
43
+ },
44
+ reverseRows: {
45
+ before: "margin-bottom",
46
+ after: "margin-top"
47
+ }
18
48
  };
49
+ const calculateGap = ({ direction, type, value }) => {
50
+ if (!direction || !type || type === "content") return void 0;
51
+ return `${gapDimensions[direction][type]}: ${value};`;
52
+ };
53
+ const styles$2 = ({ css, theme: t, rootSize }) => css`
54
+ ${alignContent({
55
+ direction: t.direction,
56
+ alignX: t.alignX,
57
+ alignY: t.alignY
58
+ })};
19
59
 
20
- /* eslint-disable import/prefer-default-export */
21
- const isWebFixNeeded = (tag) => {
22
- if (tag && tag in INLINE_ELEMENTS_FLEX_FIX)
23
- return true;
24
- return false;
60
+ ${t.equalCols && equalColsCSS};
61
+
62
+ ${t.gap && t.contentType && calculateGap({
63
+ direction: t.parentDirection,
64
+ type: t.contentType,
65
+ value: value(t.gap, rootSize)
66
+ })};
67
+
68
+ ${t.extraStyles && extendCss(t.extraStyles)};
69
+ `;
70
+ const StyledComponent = styled$2(component$2)`
71
+ ${`box-sizing: border-box;`};
72
+
73
+ display: flex;
74
+ align-self: stretch;
75
+ flex-wrap: wrap;
76
+
77
+ ${({ $contentType }) => $contentType === "content" && typeContentCSS};
78
+
79
+ ${makeItResponsive({
80
+ key: "$element",
81
+ styles: styles$2,
82
+ css: css$2,
83
+ normalize: true
84
+ })};
85
+ `;
86
+
87
+ //#endregion
88
+ //#region src/helpers/Content/component.tsx
89
+ const Component$9 = ({ contentType, tag, parentDirection, direction, alignX, alignY, equalCols, gap, extendCss, ...props }) => {
90
+ return /* @__PURE__ */ jsx(StyledComponent, {
91
+ as: tag,
92
+ $contentType: contentType,
93
+ $element: {
94
+ contentType,
95
+ parentDirection,
96
+ direction,
97
+ alignX,
98
+ alignY,
99
+ equalCols,
100
+ gap,
101
+ extraStyles: extendCss
102
+ },
103
+ ...IS_DEVELOPMENT ? { "data-vl-element": contentType } : {},
104
+ ...props
105
+ });
25
106
  };
107
+ var component_default = memo(Component$9);
26
108
 
27
- const { styled: styled$2, css: css$2, component: component$3 } = config;
109
+ //#endregion
110
+ //#region src/helpers/Content/index.ts
111
+ var Content_default = component_default;
112
+
113
+ //#endregion
114
+ //#region src/helpers/Wrapper/styled.ts
115
+ /**
116
+ * Styled component for the Element wrapper layer. Handles responsive
117
+ * block/inline-flex display, direction, alignment, and custom CSS injection.
118
+ * Includes special handling for the `parentFix` / `childFix` flags that
119
+ * split flex behavior across two DOM nodes for button/fieldset/legend
120
+ * elements where a single flex container is insufficient.
121
+ */
122
+ const { styled: styled$1, css: css$1, component: component$1 } = config;
28
123
  const childFixCSS = `
29
124
  display: flex;
30
125
  flex: 1;
@@ -43,14 +138,14 @@ const fullHeightCSS = `
43
138
  const blockCSS = `
44
139
  align-self: stretch;
45
140
  `;
46
- const childFixPosition = (isBlock) => `display: ${isBlock ? 'flex' : 'inline-flex'};`;
47
- const styles$2 = ({ theme: t, css, }) => css `
48
- ${t.alignY === 'block' && fullHeightCSS};
141
+ const childFixPosition = (isBlock) => `display: ${isBlock ? "flex" : "inline-flex"};`;
142
+ const styles$1 = ({ theme: t, css }) => css`
143
+ ${t.alignY === "block" && fullHeightCSS};
49
144
 
50
145
  ${alignContent({
51
- direction: t.direction,
52
- alignX: t.alignX,
53
- alignY: t.alignY,
146
+ direction: t.direction,
147
+ alignX: t.alignX,
148
+ alignY: t.alignY
54
149
  })};
55
150
 
56
151
  ${t.block && blockCSS};
@@ -61,1223 +156,1258 @@ const styles$2 = ({ theme: t, css, }) => css `
61
156
 
62
157
  ${t.extraStyles && extendCss(t.extraStyles)};
63
158
  `;
64
- const platformCSS$1 = `box-sizing: border-box;` ;
65
- var Styled$1 = styled$2(component$3) `
159
+ const platformCSS = `box-sizing: border-box;`;
160
+ var styled_default$1 = styled$1(component$1)`
66
161
  position: relative;
67
- ${platformCSS$1};
162
+ ${platformCSS};
68
163
 
69
164
  ${({ $childFix }) => $childFix && childFixCSS};
70
165
 
71
166
  ${makeItResponsive({
72
- key: '$element',
73
- styles: styles$2,
74
- css: css$2,
75
- normalize: true,
167
+ key: "$element",
168
+ styles: styles$1,
169
+ css: css$1,
170
+ normalize: true
76
171
  })};
77
172
  `;
78
173
 
79
- // eslint-disable-next-line react/display-name
80
- const Component$9 = forwardRef(({ children, tag, block, extendCss, direction, alignX, alignY, equalCols, isInline, ...props }, ref) => {
81
- const debugProps = IS_DEVELOPMENT
82
- ? {
83
- 'data-vl-element': 'Element',
84
- }
85
- : {};
86
- const COMMON_PROPS = {
87
- ...props,
88
- ...debugProps,
89
- ref,
90
- as: tag,
91
- };
92
- const needsFix = !props.dangerouslySetInnerHTML && isWebFixNeeded(tag)
93
- ;
94
- if (!needsFix || false) {
95
- return (React.createElement(Styled$1, { ...COMMON_PROPS, "$element": {
96
- block,
97
- direction,
98
- alignX,
99
- alignY,
100
- equalCols,
101
- extraStyles: extendCss,
102
- } }, children));
103
- }
104
- // eslint-disable-next-line no-nested-ternary
105
- const asTag = (isInline ? 'span' : 'div') ;
106
- return (React.createElement(Styled$1, { ...COMMON_PROPS, "$element": {
107
- parentFix: true,
108
- block,
109
- extraStyles: extendCss,
110
- } },
111
- React.createElement(Styled$1, { as: asTag, "$childFix": true, "$element": {
112
- childFix: true,
113
- direction,
114
- alignX,
115
- alignY,
116
- equalCols,
117
- } }, children)));
118
- });
119
-
120
- const { styled: styled$1, css: css$1, component: component$2 } = config;
121
- const equalColsCSS = `
122
- flex: 1;
123
- `;
124
- const typeContentCSS = `
125
- flex: 1;
126
- `;
127
- // --------------------------------------------------------
128
- // calculate spacing between before / content / after
129
- // --------------------------------------------------------
130
- const gapDimensions = {
131
- inline: {
132
- before: 'margin-right',
133
- after: 'margin-left',
134
- },
135
- reverseInline: {
136
- before: 'margin-right',
137
- after: 'margin-left',
138
- },
139
- rows: {
140
- before: 'margin-bottom',
141
- after: 'margin-top',
142
- },
143
- reverseRows: {
144
- before: 'margin-bottom',
145
- after: 'margin-top',
146
- },
147
- };
148
- const calculateGap = ({ direction, type, value, }) => {
149
- if (!direction || !type)
150
- return undefined;
151
- const finalStyles = `${gapDimensions[direction][type]}: ${value};`;
152
- return finalStyles;
174
+ //#endregion
175
+ //#region src/helpers/Wrapper/constants.ts
176
+ /**
177
+ * HTML elements that need a two-layer DOM workaround because browsers do not
178
+ * fully support flexbox layout on button, fieldset, and legend elements.
179
+ * @see https://stackoverflow.com/questions/35464067/flexbox-not-working-on-button-or-fieldset-elements
180
+ */
181
+ const INLINE_ELEMENTS_FLEX_FIX = {
182
+ button: true,
183
+ fieldset: true,
184
+ legend: true
153
185
  };
154
- // --------------------------------------------------------
155
- // calculations of styles to be rendered
156
- // --------------------------------------------------------
157
- const styles$1 = ({ css, theme: t, rootSize, }) => css `
158
- ${alignContent({
159
- direction: t.direction,
160
- alignX: t.alignX,
161
- alignY: t.alignY,
162
- })};
163
-
164
- ${t.equalCols && equalColsCSS};
165
-
166
- ${t.gap &&
167
- t.contentType &&
168
- calculateGap({
169
- direction: t.parentDirection,
170
- type: t.contentType,
171
- value: value(t.gap, rootSize),
172
- })};
173
-
174
- ${t.extraStyles && extendCss(t.extraStyles)};
175
- `;
176
- const platformCSS = `box-sizing: border-box;` ;
177
- const StyledComponent = styled$1(component$2) `
178
- ${platformCSS};
179
186
 
180
- display: flex;
181
- align-self: stretch;
182
- flex-wrap: wrap;
183
-
184
- ${({ $contentType }) => $contentType === 'content' && typeContentCSS};
187
+ //#endregion
188
+ //#region src/helpers/Wrapper/utils.ts
189
+ const isWebFixNeeded = (tag) => {
190
+ if (tag && tag in INLINE_ELEMENTS_FLEX_FIX) return true;
191
+ return false;
192
+ };
185
193
 
186
- ${makeItResponsive({
187
- key: '$element',
188
- styles: styles$1,
189
- css: css$1,
190
- normalize: true,
191
- })};
192
- `;
194
+ //#endregion
195
+ //#region src/helpers/Wrapper/component.tsx
196
+ /**
197
+ * 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
200
+ * (parent + child Styled) because these HTML elements do not natively
201
+ * support `display: flex` consistently across browsers.
202
+ */
203
+ 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) => {
205
+ const COMMON_PROPS = {
206
+ ...props,
207
+ ...DEV_PROPS,
208
+ ref,
209
+ as: tag
210
+ };
211
+ if (!(!props.dangerouslySetInnerHTML && isWebFixNeeded(tag)) || false) return /* @__PURE__ */ jsx(styled_default$1, {
212
+ ...COMMON_PROPS,
213
+ $element: {
214
+ block,
215
+ direction,
216
+ alignX,
217
+ alignY,
218
+ equalCols,
219
+ extraStyles: extendCss
220
+ },
221
+ children
222
+ });
223
+ const asTag = isInline ? "span" : "div";
224
+ return /* @__PURE__ */ jsx(styled_default$1, {
225
+ ...COMMON_PROPS,
226
+ $element: {
227
+ parentFix: true,
228
+ block,
229
+ extraStyles: extendCss
230
+ },
231
+ children: /* @__PURE__ */ jsx(styled_default$1, {
232
+ as: asTag,
233
+ $childFix: true,
234
+ $element: {
235
+ childFix: true,
236
+ direction,
237
+ alignX,
238
+ alignY,
239
+ equalCols
240
+ },
241
+ children
242
+ })
243
+ });
244
+ });
193
245
 
194
- const Component$8 = ({ contentType, tag, parentDirection, direction, alignX, alignY, equalCols, gap, extendCss, ...props }) => {
195
- const debugProps = IS_DEVELOPMENT
196
- ? {
197
- 'data-vl-element': contentType,
198
- }
199
- : {};
200
- const stylingProps = {
201
- contentType,
202
- parentDirection,
203
- direction,
204
- alignX,
205
- alignY,
206
- equalCols,
207
- gap,
208
- extraStyles: extendCss,
209
- };
210
- return (React.createElement(StyledComponent, { as: tag, "$contentType": contentType, "$element": stylingProps, ...debugProps, ...props }));
211
- };
212
- var component$1 = memo(Component$8);
246
+ //#endregion
247
+ //#region src/helpers/Wrapper/index.ts
248
+ var Wrapper_default = Component$8;
213
249
 
250
+ //#endregion
251
+ //#region src/Element/constants.ts
252
+ /**
253
+ * HTML tags that are inline-level by default. When Element renders one of
254
+ * these tags, child Content wrappers use `span` instead of `div` to
255
+ * preserve valid HTML nesting.
256
+ */
214
257
  const INLINE_ELEMENTS = {
215
- span: true,
216
- a: true,
217
- button: true,
218
- input: true,
219
- label: true,
220
- select: true,
221
- textarea: true,
222
- br: true,
223
- img: true,
224
- strong: true,
225
- small: true,
226
- code: true,
227
- b: true,
228
- big: true,
229
- i: true,
230
- tt: true,
231
- abbr: true,
232
- acronym: true,
233
- cite: true,
234
- dfn: true,
235
- em: true,
236
- kbd: true,
237
- samp: true,
238
- var: true,
239
- bdo: true,
240
- map: true,
241
- object: true,
242
- q: true,
243
- script: true,
244
- sub: true,
245
- sup: true,
258
+ span: true,
259
+ a: true,
260
+ button: true,
261
+ input: true,
262
+ label: true,
263
+ select: true,
264
+ textarea: true,
265
+ br: true,
266
+ img: true,
267
+ strong: true,
268
+ small: true,
269
+ code: true,
270
+ b: true,
271
+ big: true,
272
+ i: true,
273
+ tt: true,
274
+ abbr: true,
275
+ acronym: true,
276
+ cite: true,
277
+ dfn: true,
278
+ em: true,
279
+ kbd: true,
280
+ samp: true,
281
+ var: true,
282
+ bdo: true,
283
+ map: true,
284
+ object: true,
285
+ q: true,
286
+ script: true,
287
+ sub: true,
288
+ sup: true
246
289
  };
290
+ /**
291
+ * HTML void/self-closing elements that cannot have children. When Element
292
+ * detects one of these tags, it skips rendering beforeContent/content/afterContent
293
+ * and returns the Wrapper alone.
294
+ */
247
295
  const EMPTY_ELEMENTS = {
248
- area: true,
249
- base: true,
250
- br: true,
251
- col: true,
252
- embed: true,
253
- hr: true,
254
- img: true,
255
- input: true,
256
- keygen: true,
257
- link: true,
258
- textarea: true,
259
- // 'meta': true,
260
- // 'param': true,
261
- source: true,
262
- track: true,
263
- wbr: true,
296
+ area: true,
297
+ base: true,
298
+ br: true,
299
+ col: true,
300
+ embed: true,
301
+ hr: true,
302
+ img: true,
303
+ input: true,
304
+ keygen: true,
305
+ link: true,
306
+ textarea: true,
307
+ source: true,
308
+ track: true,
309
+ wbr: true
264
310
  };
265
311
 
312
+ //#endregion
313
+ //#region src/Element/utils.ts
314
+ /** Checks whether the given HTML tag is an inline-level element, used to determine sub-tag nesting. */
266
315
  const isInlineElement = (tag) => {
267
- if (tag && tag in INLINE_ELEMENTS)
268
- return true;
269
- return false;
316
+ if (tag && tag in INLINE_ELEMENTS) return true;
317
+ return false;
270
318
  };
319
+ /** Checks whether the given HTML tag is a void element that cannot have children. */
271
320
  const getShouldBeEmpty = (tag) => {
272
- if (tag && tag in EMPTY_ELEMENTS)
273
- return true;
274
- return false;
321
+ if (tag && tag in EMPTY_ELEMENTS) return true;
322
+ return false;
275
323
  };
276
324
 
277
- const defaultDirection = 'inline';
278
- const defaultContentDirection = 'rows';
279
- const defaultAlignX = 'left';
280
- const defaultAlignY = 'center';
325
+ //#endregion
326
+ //#region src/Element/component.tsx
327
+ /**
328
+ * Core building block of the elements package. Renders a three-section layout
329
+ * (beforeContent / content / afterContent) inside a flex Wrapper. When only
330
+ * content is present, the Wrapper inherits content-level alignment directly
331
+ * to avoid an unnecessary nesting layer. Handles HTML-specific edge cases
332
+ * like void elements (input, img) and inline elements (span, a) by
333
+ * skipping children or switching sub-tags accordingly.
334
+ */
335
+ const defaultDirection = "inline";
336
+ const defaultContentDirection = "rows";
337
+ const defaultAlignX = "left";
338
+ const defaultAlignY = "center";
281
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) => {
282
- // --------------------------------------------------------
283
- // check if should render only single element
284
- // --------------------------------------------------------
285
- const shouldBeEmpty = !!props.dangerouslySetInnerHTML || getShouldBeEmpty(tag)
286
- ;
287
- // --------------------------------------------------------
288
- // if not single element, calculate values
289
- // --------------------------------------------------------
290
- const isSimpleElement = !beforeContent && !afterContent;
291
- const CHILDREN = children ?? content ?? label;
292
- const isInline = isInlineElement(tag) ;
293
- const SUB_TAG = isInline ? 'span' : undefined;
294
- // --------------------------------------------------------
295
- // direction & alignX & alignY calculations
296
- // --------------------------------------------------------
297
- const { wrapperDirection, wrapperAlignX, wrapperAlignY } = useMemo(() => {
298
- let wrapperDirection = direction;
299
- let wrapperAlignX = alignX;
300
- let wrapperAlignY = alignY;
301
- if (isSimpleElement) {
302
- if (contentDirection)
303
- wrapperDirection = contentDirection;
304
- if (contentAlignX)
305
- wrapperAlignX = contentAlignX;
306
- if (contentAlignY)
307
- wrapperAlignY = contentAlignY;
308
- }
309
- else if (direction) {
310
- wrapperDirection = direction;
311
- }
312
- else {
313
- wrapperDirection = defaultDirection;
314
- }
315
- return { wrapperDirection, wrapperAlignX, wrapperAlignY };
316
- }, [
317
- isSimpleElement,
318
- contentDirection,
319
- contentAlignX,
320
- contentAlignY,
321
- alignX,
322
- alignY,
323
- direction,
324
- ]);
325
- // --------------------------------------------------------
326
- // common wrapper props
327
- // --------------------------------------------------------
328
- const WRAPPER_PROPS = {
329
- ref: ref ?? innerRef,
330
- extendCss: css,
331
- tag,
332
- block,
333
- direction: wrapperDirection,
334
- alignX: wrapperAlignX,
335
- alignY: wrapperAlignY,
336
- as: undefined, // reset styled-components `as` prop
337
- };
338
- // --------------------------------------------------------
339
- // return simple/empty element like input or image etc.
340
- // --------------------------------------------------------
341
- if (shouldBeEmpty) {
342
- return React.createElement(Component$9, { ...props, ...WRAPPER_PROPS });
343
- }
344
- const contentRenderOutput = render(CHILDREN);
345
- return (React.createElement(Component$9, { ...props, ...WRAPPER_PROPS, isInline: isInline },
346
- beforeContent && (React.createElement(component$1, { tag: SUB_TAG, contentType: "before", parentDirection: wrapperDirection, extendCss: beforeContentCss, direction: beforeContentDirection, alignX: beforeContentAlignX, alignY: beforeContentAlignY, equalCols: equalCols, gap: gap }, render(beforeContent))),
347
- isSimpleElement ? (contentRenderOutput) : (React.createElement(component$1, { tag: SUB_TAG, contentType: "content", parentDirection: wrapperDirection, extendCss: contentCss, direction: contentDirection, alignX: contentAlignX, alignY: contentAlignY, equalCols: equalCols }, contentRenderOutput)),
348
- afterContent && (React.createElement(component$1, { tag: SUB_TAG, contentType: "after", parentDirection: wrapperDirection, extendCss: afterContentCss, direction: afterContentDirection, alignX: afterContentAlignX, alignY: afterContentAlignY, equalCols: equalCols, gap: gap }, render(afterContent)))));
340
+ const shouldBeEmpty = !!props.dangerouslySetInnerHTML || getShouldBeEmpty(tag);
341
+ const isSimpleElement = !beforeContent && !afterContent;
342
+ const CHILDREN = children ?? content ?? label;
343
+ const isInline = isInlineElement(tag);
344
+ const SUB_TAG = isInline ? "span" : void 0;
345
+ const { wrapperDirection, wrapperAlignX, wrapperAlignY } = useMemo(() => {
346
+ let wrapperDirection = direction;
347
+ let wrapperAlignX = alignX;
348
+ let wrapperAlignY = alignY;
349
+ if (isSimpleElement) {
350
+ if (contentDirection) wrapperDirection = contentDirection;
351
+ if (contentAlignX) wrapperAlignX = contentAlignX;
352
+ if (contentAlignY) wrapperAlignY = contentAlignY;
353
+ } else if (direction) wrapperDirection = direction;
354
+ else wrapperDirection = defaultDirection;
355
+ return {
356
+ wrapperDirection,
357
+ wrapperAlignX,
358
+ wrapperAlignY
359
+ };
360
+ }, [
361
+ isSimpleElement,
362
+ contentDirection,
363
+ contentAlignX,
364
+ contentAlignY,
365
+ alignX,
366
+ alignY,
367
+ direction
368
+ ]);
369
+ const WRAPPER_PROPS = {
370
+ ref: ref ?? innerRef,
371
+ extendCss: css,
372
+ tag,
373
+ block,
374
+ direction: wrapperDirection,
375
+ alignX: wrapperAlignX,
376
+ alignY: wrapperAlignY,
377
+ as: void 0
378
+ };
379
+ if (shouldBeEmpty) return /* @__PURE__ */ jsx(Wrapper_default, {
380
+ ...props,
381
+ ...WRAPPER_PROPS
382
+ });
383
+ const contentRenderOutput = render(CHILDREN);
384
+ return /* @__PURE__ */ jsxs(Wrapper_default, {
385
+ ...props,
386
+ ...WRAPPER_PROPS,
387
+ isInline,
388
+ children: [
389
+ beforeContent && /* @__PURE__ */ jsx(Content_default, {
390
+ tag: SUB_TAG,
391
+ contentType: "before",
392
+ parentDirection: wrapperDirection,
393
+ extendCss: beforeContentCss,
394
+ direction: beforeContentDirection,
395
+ alignX: beforeContentAlignX,
396
+ alignY: beforeContentAlignY,
397
+ equalCols,
398
+ gap,
399
+ children: render(beforeContent)
400
+ }),
401
+ isSimpleElement ? contentRenderOutput : /* @__PURE__ */ jsx(Content_default, {
402
+ tag: SUB_TAG,
403
+ contentType: "content",
404
+ parentDirection: wrapperDirection,
405
+ extendCss: contentCss,
406
+ direction: contentDirection,
407
+ alignX: contentAlignX,
408
+ alignY: contentAlignY,
409
+ equalCols,
410
+ children: contentRenderOutput
411
+ }),
412
+ afterContent && /* @__PURE__ */ jsx(Content_default, {
413
+ tag: SUB_TAG,
414
+ contentType: "after",
415
+ parentDirection: wrapperDirection,
416
+ extendCss: afterContentCss,
417
+ direction: afterContentDirection,
418
+ alignX: afterContentAlignX,
419
+ alignY: afterContentAlignY,
420
+ equalCols,
421
+ gap,
422
+ children: render(afterContent)
423
+ })
424
+ ]
425
+ });
349
426
  });
350
427
  const name$5 = `${PKG_NAME}/Element`;
351
428
  Component$7.displayName = name$5;
352
429
  Component$7.pkgName = PKG_NAME;
353
430
  Component$7.VITUS_LABS__COMPONENT = name$5;
354
431
 
355
- const isNumber = (a, b) => Number.isInteger(a) && Number.isInteger(b);
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
+ */
356
441
  const types = {
357
- height: 'offsetHeight',
358
- width: 'offsetWidth',
442
+ height: "offsetHeight",
443
+ width: "offsetWidth"
359
444
  };
360
- const calculate = ({ beforeContent, afterContent }) => (type) => {
361
- const beforeContentSize = get(beforeContent, types[type]);
362
- const afterContentSize = get(afterContent, types[type]);
363
- if (isNumber(beforeContentSize, afterContentSize)) {
364
- if (beforeContentSize > afterContentSize) {
365
- beforeContent.style[type] = `${beforeContentSize}px`;
366
- afterContent.style[type] = `${beforeContentSize}px`;
367
- }
368
- else {
369
- beforeContent.style[type] = `${afterContentSize}px`;
370
- afterContent.style[type] = `${afterContentSize}px`;
371
- }
372
- }
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
+ }
373
454
  };
374
455
  const withEqualBeforeAfter = (WrappedComponent) => {
375
- const displayName = WrappedComponent.displayName ?? WrappedComponent.name ?? 'Component';
376
- const Enhanced = (props) => {
377
- const { equalBeforeAfter, direction, afterContent, beforeContent, ...rest } = props;
378
- const elementRef = createRef();
379
- const calculateSize = () => {
380
- const beforeContent = get(elementRef, 'current.children[0]');
381
- const afterContent = get(elementRef, 'current.children[2]');
382
- if (beforeContent && afterContent) {
383
- const updateElement = calculate({ beforeContent, afterContent });
384
- if (direction === 'rows')
385
- updateElement('height');
386
- else
387
- updateElement('width');
388
- }
389
- };
390
- if (equalBeforeAfter)
391
- calculateSize();
392
- return (React.createElement(WrappedComponent, { ...rest, afterContent: afterContent, beforeContent: beforeContent,
393
- // @ts-ignore
394
- ref: elementRef }));
395
- };
396
- Enhanced.displayName = `withEqualSizeBeforeAfter(${displayName})`;
397
- return Enhanced;
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;
398
477
  };
399
478
 
479
+ //#endregion
480
+ //#region src/Element/index.ts
481
+ var Element_default = Component$7;
482
+
483
+ //#endregion
484
+ //#region src/helpers/Iterator/component.tsx
485
+ /**
486
+ * Data-driven list renderer that supports three input modes: React children
487
+ * (including fragments), an array of primitives, or an array of objects.
488
+ * Each item receives positional metadata (first, last, odd, even, position)
489
+ * and optional injected props via `itemProps`. Items can be individually
490
+ * wrapped with `wrapComponent`. Children always take priority over the
491
+ * component+data prop pattern.
492
+ */
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
+ };
400
514
  const RESERVED_PROPS = [
401
- 'children',
402
- 'component',
403
- 'wrapComponent',
404
- 'data',
405
- 'itemKey',
406
- 'valueName',
407
- 'itemProps',
408
- 'wrapProps',
515
+ "children",
516
+ "component",
517
+ "wrapComponent",
518
+ "data",
519
+ "itemKey",
520
+ "valueName",
521
+ "itemProps",
522
+ "wrapProps"
409
523
  ];
410
- const attachItemProps = ({ i, length, }) => {
411
- const position = i + 1;
412
- return {
413
- index: i,
414
- first: position === 1,
415
- last: position === length,
416
- odd: position % 2 === 1,
417
- even: position % 2 === 0,
418
- position,
419
- };
524
+ const attachItemProps = ({ i, length }) => {
525
+ const position = i + 1;
526
+ return {
527
+ index: i,
528
+ first: position === 1,
529
+ last: position === length,
530
+ odd: position % 2 === 1,
531
+ even: position % 2 === 0,
532
+ position
533
+ };
420
534
  };
421
535
  const Component$6 = (props) => {
422
- const { itemKey, valueName, children, component, data, wrapComponent: Wrapper, wrapProps, itemProps, } = props;
423
- const injectItemProps = useMemo(() => (typeof itemProps === 'function' ? itemProps : () => itemProps), [itemProps]);
424
- const injectWrapItemProps = useMemo(() => (typeof wrapProps === 'function' ? wrapProps : () => wrapProps), [wrapProps]);
425
- const getKey = useCallback((item, index) => {
426
- if (typeof itemKey === 'function')
427
- return itemKey(item, index);
428
- return index;
429
- }, [itemKey]);
430
- const renderChild = (child, total = 1, i = 0) => {
431
- if (!itemProps && !Wrapper)
432
- return child;
433
- const extendedProps = attachItemProps({
434
- i,
435
- length: total,
436
- });
437
- const finalItemProps = itemProps ? injectItemProps({}, extendedProps) : {};
438
- // if no props extension is required, just return children
439
- if (Wrapper) {
440
- const finalWrapProps = wrapProps
441
- ? injectWrapItemProps({}, extendedProps)
442
- : {};
443
- return (React.createElement(Wrapper, { key: i, ...finalWrapProps }, render(child, finalItemProps)));
444
- }
445
- return render(child, {
446
- key: i,
447
- ...finalItemProps,
448
- });
449
- };
450
- // --------------------------------------------------------
451
- // render children
452
- // --------------------------------------------------------
453
- const renderChildren = () => {
454
- if (!children)
455
- return null;
456
- // if children is Array
457
- if (Array.isArray(children)) {
458
- return Children.map(children, (item, i) => renderChild(item, children.length, i));
459
- }
460
- // if children is Fragment
461
- if (isFragment(children)) {
462
- const fragmentChildren = children?.props?.children;
463
- const childrenLength = fragmentChildren.length;
464
- return fragmentChildren.map((item, i) => renderChild(item, childrenLength, i));
465
- }
466
- // if single child
467
- return renderChild(children);
468
- };
469
- // --------------------------------------------------------
470
- // render array of strings or numbers
471
- // --------------------------------------------------------
472
- const renderSimpleArray = (data) => {
473
- const { length } = data;
474
- // if the data array is empty
475
- if (length === 0)
476
- return null;
477
- return data.map((item, i) => {
478
- const key = getKey(item, i);
479
- const keyName = valueName ?? 'children';
480
- const extendedProps = attachItemProps({
481
- i,
482
- length,
483
- });
484
- const finalItemProps = {
485
- ...(itemProps
486
- ? injectItemProps({ [keyName]: item }, extendedProps)
487
- : {}),
488
- [keyName]: item,
489
- };
490
- if (Wrapper) {
491
- const finalWrapProps = wrapProps
492
- ? injectWrapItemProps({ [keyName]: item }, extendedProps)
493
- : {};
494
- return (React.createElement(Wrapper, { key: key, ...finalWrapProps }, render(component, finalItemProps)));
495
- }
496
- return render(component, { key, ...finalItemProps });
497
- });
498
- };
499
- // --------------------------------------------------------
500
- // render array of objects
501
- // --------------------------------------------------------
502
- const renderComplexArray = (data) => {
503
- const renderData = data.filter((item) => !isEmpty(item)); // remove empty objects
504
- const { length } = renderData;
505
- // if it's empty
506
- if (renderData.length === 0)
507
- return null;
508
- const getKey = (item, index) => {
509
- if (!itemKey)
510
- return item.key ?? item.id ?? item.itemId ?? index;
511
- if (typeof itemKey === 'function')
512
- return itemKey(item, index);
513
- if (typeof itemKey === 'string')
514
- return item[itemKey];
515
- return index;
516
- };
517
- return renderData.map((item, i) => {
518
- const { component: itemComponent, ...restItem } = item;
519
- const renderItem = itemComponent ?? component;
520
- const key = getKey(restItem, i);
521
- const extendedProps = attachItemProps({
522
- i,
523
- length,
524
- });
525
- const finalItemProps = {
526
- ...(itemProps ? injectItemProps(item, extendedProps) : {}),
527
- ...restItem,
528
- };
529
- if (Wrapper && !itemComponent) {
530
- const finalWrapProps = wrapProps
531
- ? injectWrapItemProps(item, extendedProps)
532
- : {};
533
- return (React.createElement(Wrapper, { key: key, ...finalWrapProps }, render(renderItem, finalItemProps)));
534
- }
535
- return render(renderItem, { key, ...finalItemProps });
536
- });
537
- };
538
- // --------------------------------------------------------
539
- // render list items
540
- // --------------------------------------------------------
541
- const renderItems = () => {
542
- // --------------------------------------------------------
543
- // children have priority over props component + data
544
- // --------------------------------------------------------
545
- if (children)
546
- return renderChildren();
547
- // --------------------------------------------------------
548
- // render props component + data
549
- // --------------------------------------------------------
550
- if (component && Array.isArray(data)) {
551
- const clearData = data.filter((item) => item !== null && item !== undefined);
552
- const isSimpleArray = clearData.every((item) => typeof item === 'string' || typeof item === 'number');
553
- if (isSimpleArray)
554
- return renderSimpleArray(clearData);
555
- const isComplexArray = clearData.every((item) => typeof item === 'object');
556
- if (isComplexArray)
557
- return renderComplexArray(clearData);
558
- return null;
559
- }
560
- // --------------------------------------------------------
561
- // if there are no children or valid react component and data as an array,
562
- // return null to prevent error
563
- // --------------------------------------------------------
564
- return null;
565
- };
566
- return renderItems();
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;
598
+ };
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
+ });
623
+ };
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;
633
+ };
634
+ return renderItems();
567
635
  };
568
636
  Component$6.isIterator = true;
569
637
  Component$6.RESERVED_PROPS = RESERVED_PROPS;
570
638
 
639
+ //#endregion
640
+ //#region src/helpers/Iterator/index.ts
641
+ var Iterator_default = Component$6;
642
+
643
+ //#endregion
644
+ //#region src/List/component.tsx
645
+ /**
646
+ * List component that combines Iterator (data-driven rendering) with an
647
+ * optional Element root wrapper. When `rootElement` is false (default),
648
+ * it renders a bare Iterator as a fragment. When true, the Iterator output
649
+ * is wrapped in an Element that receives all non-iterator props (e.g.,
650
+ * layout, alignment, css), allowing the list to be styled as a single block.
651
+ */
571
652
  const Component$5 = forwardRef(({ rootElement = false, ...props }, ref) => {
572
- const renderedList = React.createElement(Component$6, { ...pick(props, Component$6.RESERVED_PROPS) });
573
- if (!rootElement)
574
- return renderedList;
575
- return (React.createElement(Component$7, { ref: ref, ...omit(props, Component$6.RESERVED_PROPS) }, renderedList));
653
+ const renderedList = /* @__PURE__ */ jsx(Iterator_default, { ...pick(props, Iterator_default.RESERVED_PROPS) });
654
+ if (!rootElement) return renderedList;
655
+ return /* @__PURE__ */ jsx(Element_default, {
656
+ ref,
657
+ ...omit(props, Iterator_default.RESERVED_PROPS),
658
+ children: renderedList
659
+ });
576
660
  });
577
661
  const name$4 = `${PKG_NAME}/List`;
578
662
  Component$5.displayName = name$4;
579
663
  Component$5.pkgName = PKG_NAME;
580
664
  Component$5.VITUS_LABS__COMPONENT = name$4;
581
665
 
582
- // @ts-nocheck
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
+ */
583
676
  const RESERVED_KEYS = [
584
- 'type',
585
- 'activeItems',
586
- 'itemProps',
587
- 'activeItemRequired',
677
+ "type",
678
+ "activeItems",
679
+ "itemProps",
680
+ "activeItemRequired"
588
681
  ];
589
682
  const component = (WrappedComponent) => {
590
- const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
591
- const Enhanced = (props) => {
592
- const { type = 'single', activeItemRequired, activeItems, itemProps = {}, ...rest } = props;
593
- const initActiveItems = () => {
594
- if (type === 'single') {
595
- if (Array.isArray(activeItems)) {
596
- // eslint-disable-next-line no-console
597
- console.warn('Iterator is type of single. activeItems cannot be an array.');
598
- }
599
- else {
600
- return activeItems;
601
- }
602
- }
603
- else if (type === 'multi') {
604
- const activeItemsHelper = Array.isArray(activeItems)
605
- ? activeItems
606
- : [activeItems];
607
- return new Map(activeItemsHelper.map((id) => [id, true]));
608
- }
609
- return undefined;
610
- };
611
- const [innerActiveItems, setActiveItems] = useState(initActiveItems());
612
- const countActiveItems = (data) => {
613
- let result = 0;
614
- data.forEach((value) => {
615
- if (value)
616
- result += 1;
617
- });
618
- return result;
619
- };
620
- const updateItemState = (key) => {
621
- if (type === 'single') {
622
- setActiveItems((prevState) => {
623
- if (activeItemRequired)
624
- return key;
625
- if (prevState === key)
626
- return undefined;
627
- return key;
628
- });
629
- }
630
- else if (type === 'multi') {
631
- setActiveItems((prevState) => {
632
- // TODO: add conditional type to fix this
633
- const activeItems = new Map(prevState);
634
- if (activeItemRequired &&
635
- activeItems.get(key) &&
636
- countActiveItems(activeItems) === 1) {
637
- return activeItems;
638
- }
639
- activeItems.set(key, !activeItems.get(key));
640
- return activeItems;
641
- });
642
- }
643
- else {
644
- setActiveItems(undefined);
645
- }
646
- };
647
- const handleItemActive = (key) => {
648
- updateItemState(key);
649
- };
650
- const updateAllItemsState = (status) => {
651
- {
652
- setActiveItems(new Map());
653
- }
654
- };
655
- const setItemActive = (key) => {
656
- updateItemState(key);
657
- };
658
- const unsetItemActive = (key) => {
659
- updateItemState(key);
660
- };
661
- const toggleItemActive = (key) => {
662
- updateItemState(key);
663
- };
664
- // const setAllItemsActive = () => {
665
- // updateAllItemsState(true)
666
- // }
667
- const unsetAllItemsActive = () => {
668
- updateAllItemsState();
669
- };
670
- const isItemActive = (key) => {
671
- if (!innerActiveItems)
672
- return false;
673
- if (type === 'single')
674
- return innerActiveItems === key;
675
- if (type === 'multi' && innerActiveItems instanceof Map) {
676
- return !!innerActiveItems.get(key);
677
- }
678
- return false;
679
- };
680
- const attachMultipleProps = {
681
- unsetAllItemsActive,
682
- };
683
- const attachItemProps = (props) => {
684
- const { key } = props;
685
- const defaultItemProps = typeof itemProps === 'object' ? itemProps : itemProps(props);
686
- const result = {
687
- ...defaultItemProps,
688
- active: isItemActive(key),
689
- handleItemActive: () => handleItemActive(key),
690
- setItemActive,
691
- unsetItemActive,
692
- toggleItemActive,
693
- ...(type === 'multi' ? attachMultipleProps : {}),
694
- };
695
- return result;
696
- };
697
- useEffect(() => {
698
- if (type === 'single' && Array.isArray(activeItems)) {
699
- if (process.env.NODE_ENV !== 'production') {
700
- // eslint-disable-next-line no-console
701
- console.error('When type=`single` activeItems must be a single value, not an array');
702
- }
703
- }
704
- }, [type, activeItems]);
705
- return React.createElement(WrappedComponent, { ...rest, itemProps: attachItemProps });
706
- };
707
- Enhanced.RESERVED_KEYS = RESERVED_KEYS;
708
- Enhanced.displayName = `@vitus-labs/elements/List/withActiveState(${displayName})`;
709
- return Enhanced;
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;
710
766
  };
711
767
 
712
- const Component$4 = ({ DOMLocation, tag = 'div', children, }) => {
713
- const [element, setElement] = useState();
714
- useEffect(() => {
715
- if (!tag)
716
- return undefined;
717
- const position = DOMLocation ?? document.body;
718
- const element = document.createElement(tag);
719
- setElement(element);
720
- position.appendChild(element);
721
- return () => {
722
- position.removeChild(element);
723
- };
724
- }, [tag, DOMLocation]);
725
- if (!tag || !element)
726
- return null;
727
- return createPortal(children, element);
768
+ //#endregion
769
+ //#region src/List/index.ts
770
+ var List_default = Component$5;
771
+
772
+ //#endregion
773
+ //#region src/Portal/component.ts
774
+ /**
775
+ * Portal component that creates a new DOM element on mount, appends it to
776
+ * the target location (defaults to document.body), and uses React's
777
+ * createPortal to render children into it. The DOM element is cleaned up
778
+ * on unmount. Accepts a custom DOMLocation for rendering into specific
779
+ * containers (e.g., a modal root).
780
+ */
781
+ const Component$4 = ({ DOMLocation, tag = "div", children }) => {
782
+ const [element, setElement] = useState();
783
+ useEffect(() => {
784
+ if (!tag) return void 0;
785
+ const position = DOMLocation ?? document.body;
786
+ const element = document.createElement(tag);
787
+ setElement(element);
788
+ position.appendChild(element);
789
+ return () => {
790
+ position.removeChild(element);
791
+ };
792
+ }, [tag, DOMLocation]);
793
+ if (!tag || !element) return null;
794
+ return createPortal(children, element);
728
795
  };
729
- // ----------------------------------------------
730
- // DEFINE STATICS
731
- // ----------------------------------------------
732
796
  const name$3 = `${PKG_NAME}/Portal`;
733
797
  Component$4.displayName = name$3;
734
798
  Component$4.pkgName = PKG_NAME;
735
799
  Component$4.VITUS_LABS__COMPONENT = name$3;
736
800
 
737
- const context = createContext({});
738
- const { Provider } = context;
739
- const useOverlayContext = () => useContext(context);
740
- const Component$3 = ({ children, blocked, setBlocked, setUnblocked, }) => {
741
- const ctx = useMemo(() => ({
742
- blocked,
743
- setBlocked,
744
- setUnblocked,
745
- }), [blocked, setBlocked, setUnblocked]);
746
- return React.createElement(Provider, { value: ctx }, children);
801
+ //#endregion
802
+ //#region src/Portal/index.ts
803
+ var Portal_default = Component$4;
804
+
805
+ //#endregion
806
+ //#region src/Overlay/context.tsx
807
+ /**
808
+ * Context for nested overlay coordination. When a child overlay opens, it
809
+ * sets the parent's blocked state to true, preventing the parent from
810
+ * closing in response to click/hover events that belong to the child.
811
+ */
812
+ const context$1 = createContext({});
813
+ const { Provider: Provider$1 } = context$1;
814
+ const useOverlayContext = () => useContext(context$1);
815
+ const Component = ({ children, blocked, setBlocked, setUnblocked }) => {
816
+ return /* @__PURE__ */ jsx(Provider$1, {
817
+ value: useMemo(() => ({
818
+ blocked,
819
+ setBlocked,
820
+ setUnblocked
821
+ }), [
822
+ blocked,
823
+ setBlocked,
824
+ setUnblocked
825
+ ]),
826
+ children
827
+ });
747
828
  };
748
829
 
749
- /* eslint-disable no-console */
750
- const useOverlay = ({ isOpen = false, openOn = 'click', // click | hover
751
- closeOn = 'click', // click | 'clickOnTrigger' | 'clickOutsideContent' | hover | manual
752
- type = 'dropdown', // dropdown | tooltip | popover | modal
753
- position = 'fixed', // absolute | fixed | relative | static
754
- align = 'bottom', // main align prop top | left | bottom | right
755
- alignX = 'left', // left | center | right
756
- alignY = 'bottom', // top | center | bottom
757
- offsetX = 0, offsetY = 0, throttleDelay = 200, parentContainer, closeOnEsc = true, disabled, onOpen, onClose, } = {}) => {
758
- const { rootSize } = useContext(context$1);
759
- const ctx = useOverlayContext();
760
- const [isContentLoaded, setContentLoaded] = useState(false);
761
- const [innerAlignX, setInnerAlignX] = useState(alignX);
762
- const [innerAlignY, setInnerAlignY] = useState(alignY);
763
- const [blocked, handleBlocked] = useState(false);
764
- const [active, handleActive] = useState(isOpen);
765
- const triggerRef = useRef(null);
766
- const contentRef = useRef(null);
767
- const setBlocked = useCallback(() => handleBlocked(true), []);
768
- const setUnblocked = useCallback(() => handleBlocked(false), []);
769
- const showContent = useCallback(() => {
770
- handleActive(true);
771
- }, []);
772
- const hideContent = useCallback(() => {
773
- handleActive(false);
774
- }, []);
775
- const calculateContentPosition = useCallback(() => {
776
- const overlayPosition = {};
777
- if (!active || !isContentLoaded)
778
- return overlayPosition;
779
- if (type === 'modal' && !contentRef.current) {
780
- if (IS_DEVELOPMENT) {
781
- console.warn('Cannot access `ref` of `content` component.');
782
- }
783
- return overlayPosition;
784
- }
785
- if (['dropdown', 'tooltip', 'popover'].includes(type)) {
786
- // return empty object when refs are not available
787
- if (!triggerRef.current || !contentRef.current) {
788
- if (IS_DEVELOPMENT) {
789
- console.warn('Cannot access `ref` of trigger or content component.');
790
- }
791
- return overlayPosition;
792
- }
793
- const c = contentRef.current.getBoundingClientRect();
794
- const t = triggerRef.current.getBoundingClientRect();
795
- // align is top or bottom
796
- if (['top', 'bottom'].includes(align)) {
797
- // axe Y position
798
- // (assigned as top position)
799
- const top = t.top - offsetY - c.height;
800
- const bottom = t.bottom + offsetY;
801
- // axe X position
802
- // content position to trigger position
803
- // (assigned as left position)
804
- const left = t.left + offsetX;
805
- const right = t.right - offsetX - c.width;
806
- // calculate possible position
807
- const isTop = top >= 0; // represents window.height = 0
808
- const isBottom = bottom + c.height <= window.innerHeight;
809
- const isLeft = left + c.width <= window.innerWidth;
810
- const isRight = right >= 0; // represents window.width = 0
811
- if (align === 'top') {
812
- setInnerAlignY(isTop ? 'top' : 'bottom');
813
- overlayPosition.top = isTop ? top : bottom;
814
- }
815
- else if (align === 'bottom') {
816
- setInnerAlignY(isBottom ? 'bottom' : 'top');
817
- overlayPosition.top = isBottom ? bottom : top;
818
- }
819
- // left
820
- if (alignX === 'left') {
821
- setInnerAlignX(isLeft ? 'left' : 'right');
822
- overlayPosition.left = isLeft ? left : right;
823
- }
824
- // center
825
- else if (alignX === 'center') {
826
- const center = t.left + (t.right - t.left) / 2 - c.width / 2;
827
- const isCenteredLeft = center >= 0;
828
- const isCenteredRight = center + c.width <= window.innerWidth;
829
- if (isCenteredLeft && isCenteredRight) {
830
- setInnerAlignX('center');
831
- overlayPosition.left = center;
832
- }
833
- else if (isCenteredLeft) {
834
- setInnerAlignX('left');
835
- overlayPosition.left = left;
836
- }
837
- else if (isCenteredRight) {
838
- setInnerAlignX('right');
839
- overlayPosition.left = right;
840
- }
841
- }
842
- // right
843
- else if (alignX === 'right') {
844
- setInnerAlignX(isRight ? 'right' : 'left');
845
- overlayPosition.left = isRight ? right : left;
846
- }
847
- }
848
- // align is left or right
849
- else if (['left', 'right'].includes(align)) {
850
- // axe X position
851
- // (assigned as left position)
852
- const left = t.left - offsetX - c.width;
853
- const right = t.right + offsetX;
854
- // axe Y position
855
- // content position to trigger position
856
- // (assigned as top position)
857
- const top = t.top + offsetY;
858
- const bottom = t.bottom - offsetY - c.height;
859
- const isLeft = left >= 0;
860
- const isRight = right + c.width <= window.innerWidth;
861
- const isTop = top + c.height <= window.innerHeight;
862
- const isBottom = bottom >= 0;
863
- if (align === 'left') {
864
- setInnerAlignX(isLeft ? 'left' : 'right');
865
- overlayPosition.left = isLeft ? left : right;
866
- }
867
- else if (align === 'right') {
868
- setInnerAlignX(isRight ? 'right' : 'left');
869
- overlayPosition.left = isRight ? right : left;
870
- }
871
- // top
872
- if (alignY === 'top') {
873
- setInnerAlignY(isTop ? 'top' : 'bottom');
874
- overlayPosition.top = isTop ? top : bottom;
875
- }
876
- // center
877
- else if (alignY === 'center') {
878
- const center = t.top + (t.bottom - t.top) / 2 - c.height / 2;
879
- const isCenteredTop = center >= 0;
880
- const isCenteredBottom = center + c.height <= window.innerHeight;
881
- if (isCenteredTop && isCenteredBottom) {
882
- setInnerAlignY('center');
883
- overlayPosition.top = center;
884
- }
885
- else if (isCenteredTop) {
886
- setInnerAlignY('top');
887
- overlayPosition.top = top;
888
- }
889
- else if (isCenteredBottom) {
890
- setInnerAlignY('bottom');
891
- overlayPosition.top = bottom;
892
- }
893
- }
894
- // bottom
895
- else if (alignY === 'bottom') {
896
- setInnerAlignY(isBottom ? 'bottom' : 'top');
897
- overlayPosition.top = isBottom ? bottom : top;
898
- }
899
- }
900
- }
901
- // modal type
902
- else if (type === 'modal') {
903
- // return empty object when ref is not available
904
- // triggerRef is not needed in this case
905
- if (!contentRef.current) {
906
- if (IS_DEVELOPMENT) {
907
- console.warn('Cannot access `ref` of trigger or content component.');
908
- }
909
- return overlayPosition;
910
- }
911
- const c = contentRef.current.getBoundingClientRect();
912
- switch (alignX) {
913
- case 'right':
914
- overlayPosition.right = offsetX;
915
- break;
916
- case 'left':
917
- overlayPosition.left = offsetX;
918
- break;
919
- case 'center':
920
- overlayPosition.left = window.innerWidth / 2 - c.width / 2;
921
- break;
922
- default:
923
- overlayPosition.right = offsetX;
924
- }
925
- switch (alignY) {
926
- case 'top':
927
- overlayPosition.top = offsetY;
928
- break;
929
- case 'center':
930
- overlayPosition.top = window.innerHeight / 2 - c.height / 2;
931
- break;
932
- case 'bottom':
933
- overlayPosition.bottom = offsetY;
934
- break;
935
- default:
936
- overlayPosition.top = offsetY;
937
- }
938
- }
939
- return overlayPosition;
940
- }, [
941
- isContentLoaded,
942
- active,
943
- align,
944
- alignX,
945
- alignY,
946
- offsetX,
947
- offsetY,
948
- type,
949
- triggerRef,
950
- contentRef,
951
- ]);
952
- const assignContentPosition = useCallback((values = {}) => {
953
- if (!contentRef.current)
954
- return;
955
- const isValue = (value) => {
956
- if (typeof value === 'number')
957
- return true;
958
- if (Number.isFinite(value))
959
- return true;
960
- return !!value;
961
- };
962
- const setValue = (param) => value(param, rootSize);
963
- // ADD POSITION STYLES TO CONTENT
964
- // eslint-disable-next-line no-param-reassign
965
- if (isValue(position))
966
- contentRef.current.style.position = position;
967
- // eslint-disable-next-line no-param-reassign
968
- if (isValue(values.top))
969
- contentRef.current.style.top = setValue(values.top);
970
- // eslint-disable-next-line no-param-reassign
971
- if (isValue(values.bottom))
972
- contentRef.current.style.bottom = setValue(values.bottom);
973
- // eslint-disable-next-line no-param-reassign
974
- if (isValue(values.left))
975
- contentRef.current.style.left = setValue(values.left);
976
- // eslint-disable-next-line no-param-reassign
977
- if (isValue(values.right))
978
- contentRef.current.style.right = setValue(values.right);
979
- }, [position, rootSize, contentRef]);
980
- const setContentPosition = useCallback(() => {
981
- const currentPosition = calculateContentPosition();
982
- assignContentPosition(currentPosition);
983
- }, [assignContentPosition, calculateContentPosition]);
984
- const isNodeOrChild = (ref /* | typeof contentRef */) => (e) => {
985
- if (e?.target && ref.current) {
986
- return (ref.current.contains(e.target) || e.target === ref.current);
987
- }
988
- return false;
989
- };
990
- const handleVisibilityByEventType = useCallback((e) => {
991
- if (blocked || disabled)
992
- return;
993
- const isTrigger = isNodeOrChild(triggerRef);
994
- const isContent = isNodeOrChild(contentRef);
995
- // showing content observing
996
- if (!active) {
997
- if ((openOn === 'hover' && e.type === 'mousemove') ||
998
- (openOn === 'click' && e.type === 'click')) {
999
- if (isTrigger(e)) {
1000
- showContent();
1001
- }
1002
- }
1003
- }
1004
- // hiding content observing
1005
- if (active) {
1006
- if (closeOn === 'hover' &&
1007
- e.type === 'mousemove' &&
1008
- !isTrigger(e) &&
1009
- !isContent(e)) {
1010
- hideContent();
1011
- }
1012
- if (closeOn === 'hover' && e.type === 'scroll') {
1013
- hideContent();
1014
- }
1015
- if (closeOn === 'click' && e.type === 'click') {
1016
- hideContent();
1017
- }
1018
- if (closeOn === 'clickOnTrigger' && e.type === 'click') {
1019
- if (isTrigger(e)) {
1020
- hideContent();
1021
- }
1022
- }
1023
- if (closeOn === 'clickOutsideContent' && e.type === 'click') {
1024
- if (!isContent(e)) {
1025
- hideContent();
1026
- }
1027
- }
1028
- }
1029
- }, [
1030
- active,
1031
- blocked,
1032
- disabled,
1033
- openOn,
1034
- closeOn,
1035
- hideContent,
1036
- showContent,
1037
- triggerRef,
1038
- contentRef,
1039
- ]);
1040
- const handleContentPosition = useCallback(throttle(setContentPosition, throttleDelay),
1041
- // same deps as `setContentPosition`
1042
- [assignContentPosition, calculateContentPosition]);
1043
- const handleClick = handleVisibilityByEventType;
1044
- const handleVisibility = useCallback(throttle(handleVisibilityByEventType, throttleDelay),
1045
- // same deps as `handleVisibilityByEventType`
1046
- [
1047
- active,
1048
- blocked,
1049
- disabled,
1050
- openOn,
1051
- closeOn,
1052
- hideContent,
1053
- showContent,
1054
- triggerRef,
1055
- contentRef,
1056
- ]);
1057
- // --------------------------------------------------------------------------
1058
- // useEffects
1059
- // --------------------------------------------------------------------------
1060
- useEffect(() => {
1061
- setInnerAlignX(alignX);
1062
- setInnerAlignY(alignY);
1063
- if (disabled) {
1064
- hideContent();
1065
- }
1066
- }, [disabled, alignX, alignY, hideContent]);
1067
- useEffect(() => {
1068
- if (!active || !isContentLoaded)
1069
- return undefined;
1070
- setContentPosition();
1071
- setContentPosition();
1072
- return undefined;
1073
- }, [active, isContentLoaded, setContentPosition]);
1074
- // if an Overlay has an Overlay child, this will prevent closing parent child
1075
- // and calculates correct position when an Overlay is opened
1076
- useEffect(() => {
1077
- if (active) {
1078
- if (onOpen)
1079
- onOpen();
1080
- if (ctx.setBlocked)
1081
- ctx.setBlocked();
1082
- }
1083
- else {
1084
- setContentLoaded(false);
1085
- }
1086
- return () => {
1087
- if (onClose)
1088
- onClose();
1089
- if (ctx.setUnblocked)
1090
- ctx.setUnblocked();
1091
- };
1092
- }, [active, showContent, ctx]);
1093
- // handle closing only when content is active
1094
- useEffect(() => {
1095
- if (!closeOnEsc || !active || blocked)
1096
- return undefined;
1097
- const handleEscKey = (e) => {
1098
- if (e.key === 'Escape') {
1099
- hideContent();
1100
- }
1101
- };
1102
- window.addEventListener('keydown', handleEscKey);
1103
- return () => {
1104
- window.removeEventListener('keydown', handleEscKey);
1105
- };
1106
- }, [active, blocked, closeOnEsc, hideContent]);
1107
- // handles repositioning of content on document events
1108
- useEffect(() => {
1109
- if (!active)
1110
- return undefined;
1111
- const shouldSetOverflow = type === 'modal';
1112
- const onScroll = (e) => {
1113
- handleContentPosition();
1114
- handleVisibility(e);
1115
- };
1116
- if (shouldSetOverflow)
1117
- document.body.style.overflow = 'hidden';
1118
- window.addEventListener('resize', handleContentPosition);
1119
- window.addEventListener('scroll', onScroll);
1120
- return () => {
1121
- if (shouldSetOverflow)
1122
- document.body.style.overflow = '';
1123
- window.removeEventListener('resize', handleContentPosition);
1124
- window.removeEventListener('scroll', onScroll);
1125
- };
1126
- }, [active, type, handleVisibility, handleContentPosition]);
1127
- // handles repositioning of content on a custom element if defined
1128
- useEffect(() => {
1129
- if (!active || !parentContainer)
1130
- return undefined;
1131
- // eslint-disable-next-line no-param-reassign
1132
- if (closeOn !== 'hover')
1133
- parentContainer.style.overflow = 'hidden';
1134
- const onScroll = (e) => {
1135
- handleContentPosition();
1136
- handleVisibility(e);
1137
- };
1138
- parentContainer.addEventListener('scroll', onScroll);
1139
- return () => {
1140
- // eslint-disable-next-line no-param-reassign
1141
- parentContainer.style.overflow = '';
1142
- parentContainer.removeEventListener('scroll', onScroll);
1143
- };
1144
- }, [
1145
- active,
1146
- parentContainer,
1147
- closeOn,
1148
- handleContentPosition,
1149
- handleVisibility,
1150
- ]);
1151
- // enable overlay manipulation only when the state is NOT blocked
1152
- // nor in disabled state
1153
- useEffect(() => {
1154
- if (blocked || disabled)
1155
- return undefined;
1156
- const enabledMouseMove = openOn === 'hover' || closeOn === 'hover';
1157
- const enabledClick = openOn === 'click' ||
1158
- ['click', 'clickOnTrigger', 'clickOutsideContent'].includes(closeOn);
1159
- if (enabledClick) {
1160
- window.addEventListener('click', handleClick);
1161
- }
1162
- if (enabledMouseMove) {
1163
- window.addEventListener('mousemove', handleVisibility);
1164
- }
1165
- return () => {
1166
- window.removeEventListener('click', handleClick);
1167
- window.removeEventListener('mousemove', handleVisibility);
1168
- };
1169
- }, [
1170
- openOn,
1171
- closeOn,
1172
- blocked,
1173
- disabled,
1174
- active,
1175
- handleClick,
1176
- handleVisibility,
1177
- ]);
1178
- // hack-ish way to load content correctly on the first load
1179
- // as `contentRef` is loaded dynamically
1180
- const contentRefCallback = useCallback((node) => {
1181
- if (node) {
1182
- contentRef.current = node;
1183
- setContentLoaded(true);
1184
- }
1185
- }, []);
1186
- return {
1187
- triggerRef,
1188
- contentRef: contentRefCallback,
1189
- active,
1190
- align,
1191
- alignX: innerAlignX,
1192
- alignY: innerAlignY,
1193
- showContent,
1194
- hideContent,
1195
- blocked,
1196
- setBlocked,
1197
- setUnblocked,
1198
- Provider: Component$3,
1199
- };
830
+ //#endregion
831
+ //#region src/Overlay/useOverlay.tsx
832
+ /**
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.
839
+ */
840
+ const sel = (cond, a, b) => cond ? a : b;
841
+ const devWarn = (msg) => {
842
+ if (!IS_DEVELOPMENT) return;
843
+ console.warn(msg);
844
+ };
845
+ const calcDropdownVertical = (c, t, align, alignX, offsetX, offsetY) => {
846
+ const pos = {};
847
+ const topPos = t.top - offsetY - c.height;
848
+ const bottomPos = t.bottom + offsetY;
849
+ const leftPos = t.left + offsetX;
850
+ const rightPos = t.right - offsetX - c.width;
851
+ const fitsTop = topPos >= 0;
852
+ const fitsBottom = bottomPos + c.height <= window.innerHeight;
853
+ const fitsLeft = leftPos + c.width <= window.innerWidth;
854
+ const fitsRight = rightPos >= 0;
855
+ const useTop = sel(align === "top", fitsTop, !fitsBottom);
856
+ pos.top = sel(useTop, topPos, bottomPos);
857
+ const resolvedAlignY = sel(useTop, "top", "bottom");
858
+ let resolvedAlignX = alignX;
859
+ if (alignX === "left") {
860
+ pos.left = sel(fitsLeft, leftPos, rightPos);
861
+ resolvedAlignX = sel(fitsLeft, "left", "right");
862
+ } else if (alignX === "right") {
863
+ pos.left = sel(fitsRight, rightPos, leftPos);
864
+ resolvedAlignX = sel(fitsRight, "right", "left");
865
+ } else {
866
+ const center = t.left + (t.right - t.left) / 2 - c.width / 2;
867
+ const fitsCL = center >= 0;
868
+ const fitsCR = center + c.width <= window.innerWidth;
869
+ if (fitsCL && fitsCR) {
870
+ resolvedAlignX = "center";
871
+ pos.left = center;
872
+ } else if (fitsCL) {
873
+ resolvedAlignX = "left";
874
+ pos.left = leftPos;
875
+ } else if (fitsCR) {
876
+ resolvedAlignX = "right";
877
+ pos.left = rightPos;
878
+ }
879
+ }
880
+ return {
881
+ pos,
882
+ resolvedAlignX,
883
+ resolvedAlignY
884
+ };
885
+ };
886
+ const calcDropdownHorizontal = (c, t, align, alignY, offsetX, offsetY) => {
887
+ const pos = {};
888
+ const leftPos = t.left - offsetX - c.width;
889
+ const rightPos = t.right + offsetX;
890
+ const topPos = t.top + offsetY;
891
+ const bottomPos = t.bottom - offsetY - c.height;
892
+ const fitsLeft = leftPos >= 0;
893
+ const fitsRight = rightPos + c.width <= window.innerWidth;
894
+ const fitsTop = topPos + c.height <= window.innerHeight;
895
+ const fitsBottom = bottomPos >= 0;
896
+ const useLeft = sel(align === "left", fitsLeft, !fitsRight);
897
+ pos.left = sel(useLeft, leftPos, rightPos);
898
+ const resolvedAlignX = sel(useLeft, "left", "right");
899
+ let resolvedAlignY = alignY;
900
+ if (alignY === "top") {
901
+ pos.top = sel(fitsTop, topPos, bottomPos);
902
+ resolvedAlignY = sel(fitsTop, "top", "bottom");
903
+ } else if (alignY === "bottom") {
904
+ pos.top = sel(fitsBottom, bottomPos, topPos);
905
+ resolvedAlignY = sel(fitsBottom, "bottom", "top");
906
+ } else {
907
+ const center = t.top + (t.bottom - t.top) / 2 - c.height / 2;
908
+ const fitsCT = center >= 0;
909
+ const fitsCB = center + c.height <= window.innerHeight;
910
+ if (fitsCT && fitsCB) {
911
+ resolvedAlignY = "center";
912
+ pos.top = center;
913
+ } else if (fitsCT) {
914
+ resolvedAlignY = "top";
915
+ pos.top = topPos;
916
+ } else if (fitsCB) {
917
+ resolvedAlignY = "bottom";
918
+ pos.top = bottomPos;
919
+ }
920
+ }
921
+ return {
922
+ pos,
923
+ resolvedAlignX,
924
+ resolvedAlignY
925
+ };
926
+ };
927
+ const calcModalPos = (c, alignX, alignY, offsetX, offsetY) => {
928
+ const pos = {};
929
+ switch (alignX) {
930
+ case "right":
931
+ pos.right = offsetX;
932
+ break;
933
+ case "left":
934
+ pos.left = offsetX;
935
+ break;
936
+ case "center":
937
+ pos.left = window.innerWidth / 2 - c.width / 2;
938
+ break;
939
+ default: pos.right = offsetX;
940
+ }
941
+ switch (alignY) {
942
+ case "top":
943
+ pos.top = offsetY;
944
+ break;
945
+ case "center":
946
+ pos.top = window.innerHeight / 2 - c.height / 2;
947
+ break;
948
+ case "bottom":
949
+ pos.bottom = offsetY;
950
+ break;
951
+ default: pos.top = offsetY;
952
+ }
953
+ return pos;
954
+ };
955
+ const adjustForAncestor = (pos, ancestor) => {
956
+ if (ancestor.top === 0 && ancestor.left === 0) return pos;
957
+ const result = { ...pos };
958
+ if (typeof result.top === "number") result.top -= ancestor.top;
959
+ if (typeof result.bottom === "number") result.bottom += ancestor.top;
960
+ if (typeof result.left === "number") result.left -= ancestor.left;
961
+ if (typeof result.right === "number") result.right += ancestor.left;
962
+ return result;
963
+ };
964
+ const computePosition = (type, align, alignX, alignY, offsetX, offsetY, triggerEl, contentEl, ancestorOffset) => {
965
+ const isDropdown = [
966
+ "dropdown",
967
+ "tooltip",
968
+ "popover"
969
+ ].includes(type);
970
+ if (isDropdown && (!triggerEl || !contentEl)) {
971
+ devWarn(`[@vitus-labs/elements] Overlay (${type}): ${triggerEl ? "contentRef" : "triggerRef"} is not attached. Position cannot be calculated without both refs.`);
972
+ return { pos: {} };
973
+ }
974
+ if (isDropdown && triggerEl && contentEl) {
975
+ const c = contentEl.getBoundingClientRect();
976
+ const t = triggerEl.getBoundingClientRect();
977
+ const result = align === "top" || align === "bottom" ? calcDropdownVertical(c, t, align, alignX, offsetX, offsetY) : calcDropdownHorizontal(c, t, align, alignY, offsetX, offsetY);
978
+ return {
979
+ pos: adjustForAncestor(result.pos, ancestorOffset),
980
+ resolvedAlignX: result.resolvedAlignX,
981
+ resolvedAlignY: result.resolvedAlignY
982
+ };
983
+ }
984
+ if (type === "modal") {
985
+ if (!contentEl) {
986
+ devWarn("[@vitus-labs/elements] Overlay (modal): contentRef is not attached. Modal position cannot be calculated without a content element.");
987
+ return { pos: {} };
988
+ }
989
+ return { pos: adjustForAncestor(calcModalPos(contentEl.getBoundingClientRect(), alignX, alignY, offsetX, offsetY), ancestorOffset) };
990
+ }
991
+ return { pos: {} };
992
+ };
993
+ const processVisibilityEvent = (e, active, openOn, closeOn, isTrigger, isContent, showContent, hideContent) => {
994
+ if (!active && openOn === "click" && e.type === "click" && isTrigger(e)) {
995
+ showContent();
996
+ return;
997
+ }
998
+ if (!active) return;
999
+ if (closeOn === "hover" && e.type === "scroll") {
1000
+ hideContent();
1001
+ return;
1002
+ }
1003
+ if (e.type !== "click") return;
1004
+ if (closeOn === "click") hideContent();
1005
+ else if (closeOn === "clickOnTrigger" && isTrigger(e)) hideContent();
1006
+ else if (closeOn === "clickOutsideContent" && !isContent(e)) hideContent();
1007
+ };
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 } = {}) => {
1009
+ const { rootSize } = useContext(context);
1010
+ const ctx = useOverlayContext();
1011
+ const [isContentLoaded, setContentLoaded] = useState(false);
1012
+ const [innerAlignX, setInnerAlignX] = useState(alignX);
1013
+ const [innerAlignY, setInnerAlignY] = useState(alignY);
1014
+ const [blocked, handleBlocked] = useState(false);
1015
+ const [active, handleActive] = useState(isOpen);
1016
+ const triggerRef = useRef(null);
1017
+ 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
+ }, []);
1026
+ const getAncestorOffset = useCallback(() => {
1027
+ if (position !== "absolute" || !contentRef.current) return {
1028
+ top: 0,
1029
+ left: 0
1030
+ };
1031
+ const offsetParent = contentRef.current.offsetParent;
1032
+ if (!offsetParent || offsetParent === document.body) return {
1033
+ top: 0,
1034
+ left: 0
1035
+ };
1036
+ const rect = offsetParent.getBoundingClientRect();
1037
+ return {
1038
+ top: rect.top,
1039
+ left: rect.left
1040
+ };
1041
+ }, [position]);
1042
+ const calculateContentPosition = useCallback(() => {
1043
+ if (!active || !isContentLoaded) return {};
1044
+ const result = computePosition(type, align, alignX, alignY, offsetX, offsetY, triggerRef.current, contentRef.current, getAncestorOffset());
1045
+ if (result.resolvedAlignX) setInnerAlignX(result.resolvedAlignX);
1046
+ if (result.resolvedAlignY) setInnerAlignY(result.resolvedAlignY);
1047
+ return result.pos;
1048
+ }, [
1049
+ isContentLoaded,
1050
+ active,
1051
+ align,
1052
+ alignX,
1053
+ alignY,
1054
+ offsetX,
1055
+ offsetY,
1056
+ type,
1057
+ getAncestorOffset
1058
+ ]);
1059
+ const assignContentPosition = useCallback((values = {}) => {
1060
+ if (!contentRef.current) return;
1061
+ const el = contentRef.current;
1062
+ const setValue = (param) => value(param, rootSize);
1063
+ el.style.position = position;
1064
+ el.style.top = values.top != null ? setValue(values.top) : "";
1065
+ el.style.bottom = values.bottom != null ? setValue(values.bottom) : "";
1066
+ el.style.left = values.left != null ? setValue(values.left) : "";
1067
+ el.style.right = values.right != null ? setValue(values.right) : "";
1068
+ }, [position, rootSize]);
1069
+ const setContentPosition = useCallback(() => {
1070
+ assignContentPosition(calculateContentPosition());
1071
+ }, [assignContentPosition, calculateContentPosition]);
1072
+ const isNodeOrChild = useCallback((ref) => (e) => {
1073
+ if (e?.target && ref.current) return ref.current.contains(e.target) || e.target === ref.current;
1074
+ return false;
1075
+ }, []);
1076
+ const handleVisibilityByEventType = useCallback((e) => {
1077
+ if (blocked || disabled) return;
1078
+ processVisibilityEvent(e, active, openOn, closeOn, isNodeOrChild(triggerRef), isNodeOrChild(contentRef), showContent, hideContent);
1079
+ }, [
1080
+ active,
1081
+ blocked,
1082
+ disabled,
1083
+ openOn,
1084
+ closeOn,
1085
+ hideContent,
1086
+ showContent,
1087
+ isNodeOrChild
1088
+ ]);
1089
+ const latestSetContentPosition = useRef(setContentPosition);
1090
+ latestSetContentPosition.current = setContentPosition;
1091
+ const latestHandleVisibility = useRef(handleVisibilityByEventType);
1092
+ latestHandleVisibility.current = handleVisibilityByEventType;
1093
+ const handleContentPosition = useMemo(() => throttle(() => latestSetContentPosition.current(), throttleDelay), [throttleDelay]);
1094
+ const handleClick = handleVisibilityByEventType;
1095
+ const handleVisibility = useMemo(() => throttle((e) => latestHandleVisibility.current(e), throttleDelay), [throttleDelay]);
1096
+ useEffect(() => {
1097
+ setInnerAlignX(alignX);
1098
+ setInnerAlignY(alignY);
1099
+ if (disabled) hideContent();
1100
+ }, [
1101
+ disabled,
1102
+ alignX,
1103
+ alignY,
1104
+ hideContent
1105
+ ]);
1106
+ useEffect(() => {
1107
+ if (!active || !isContentLoaded) return void 0;
1108
+ setContentPosition();
1109
+ const rafId = requestAnimationFrame(() => setContentPosition());
1110
+ return () => cancelAnimationFrame(rafId);
1111
+ }, [
1112
+ active,
1113
+ isContentLoaded,
1114
+ setContentPosition
1115
+ ]);
1116
+ const prevActiveRef = useRef(false);
1117
+ useEffect(() => {
1118
+ const wasActive = prevActiveRef.current;
1119
+ prevActiveRef.current = active;
1120
+ if (active && !wasActive) {
1121
+ onOpen?.();
1122
+ ctx.setBlocked?.();
1123
+ } else if (!active && wasActive) {
1124
+ setContentLoaded(false);
1125
+ onClose?.();
1126
+ ctx.setUnblocked?.();
1127
+ } else if (!active) setContentLoaded(false);
1128
+ return () => {
1129
+ if (active) {
1130
+ onClose?.();
1131
+ ctx.setUnblocked?.();
1132
+ }
1133
+ };
1134
+ }, [
1135
+ active,
1136
+ ctx,
1137
+ onClose,
1138
+ onOpen
1139
+ ]);
1140
+ 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
+ };
1149
+ }, [
1150
+ active,
1151
+ blocked,
1152
+ closeOnEsc,
1153
+ hideContent
1154
+ ]);
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
+ }, [
1171
+ active,
1172
+ 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
+ parentContainer,
1191
+ closeOn,
1192
+ handleContentPosition,
1193
+ handleVisibility
1194
+ ]);
1195
+ useEffect(() => {
1196
+ if (blocked || disabled) return void 0;
1197
+ if (openOn === "click" || [
1198
+ "click",
1199
+ "clickOnTrigger",
1200
+ "clickOutsideContent"
1201
+ ].includes(closeOn)) window.addEventListener("click", handleClick);
1202
+ return () => {
1203
+ window.removeEventListener("click", handleClick);
1204
+ };
1205
+ }, [
1206
+ openOn,
1207
+ closeOn,
1208
+ blocked,
1209
+ disabled,
1210
+ handleClick
1211
+ ]);
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,
1261
+ isContentLoaded,
1262
+ blocked,
1263
+ disabled,
1264
+ openOn,
1265
+ closeOn,
1266
+ showContent,
1267
+ hideContent
1268
+ ]);
1269
+ return {
1270
+ triggerRef,
1271
+ contentRef: useCallback((node) => {
1272
+ if (node) {
1273
+ contentRef.current = node;
1274
+ setContentLoaded(true);
1275
+ }
1276
+ }, []),
1277
+ active,
1278
+ align,
1279
+ alignX: innerAlignX,
1280
+ alignY: innerAlignY,
1281
+ showContent,
1282
+ hideContent,
1283
+ blocked,
1284
+ setBlocked,
1285
+ setUnblocked,
1286
+ Provider: Component
1287
+ };
1200
1288
  };
1201
1289
 
1202
- const IS_BROWSER = typeof window !== 'undefined';
1203
- const Component$2 = ({ children, trigger, DOMLocation, triggerRefName = 'ref', contentRefName = 'ref', ...props }) => {
1204
- const { active, triggerRef, contentRef, showContent, hideContent, align, alignX, alignY, Provider, ...ctx } = useOverlay(props);
1205
- const { openOn, closeOn } = props;
1206
- const passHandlers = useMemo(() => openOn === 'manual' ||
1207
- closeOn === 'manual' ||
1208
- closeOn === 'clickOutsideContent', [openOn, closeOn]);
1209
- return (React.createElement(React.Fragment, null,
1210
- render(trigger, {
1211
- [triggerRefName]: triggerRef,
1212
- active,
1213
- ...(passHandlers ? { showContent, hideContent } : {}),
1214
- }),
1215
- IS_BROWSER && active && (React.createElement(Component$4, { DOMLocation: DOMLocation },
1216
- React.createElement(Provider, { ...ctx }, render(children, {
1217
- [contentRefName]: contentRef,
1218
- active,
1219
- align,
1220
- alignX,
1221
- alignY,
1222
- ...(passHandlers ? { showContent, hideContent } : {}),
1223
- }))))));
1290
+ //#endregion
1291
+ //#region src/Overlay/component.tsx
1292
+ /**
1293
+ * Overlay component that renders a trigger element and conditionally shows
1294
+ * content via a Portal. The trigger receives a ref and optional show/hide
1295
+ * callbacks; the content is positioned and managed by the useOverlay hook.
1296
+ * A context Provider wraps the content to support nested overlays (e.g.,
1297
+ * a dropdown inside another dropdown) via blocked-state propagation.
1298
+ */
1299
+ const IS_BROWSER = typeof window !== "undefined";
1300
+ const Component$3 = ({ children, trigger, DOMLocation, triggerRefName = "ref", contentRefName = "ref", ...props }) => {
1301
+ const { active, triggerRef, contentRef, showContent, hideContent, align, alignX, alignY, Provider, ...ctx } = useOverlay(props);
1302
+ const { openOn, closeOn } = props;
1303
+ const passHandlers = useMemo(() => openOn === "manual" || closeOn === "manual" || closeOn === "clickOutsideContent", [openOn, closeOn]);
1304
+ return /* @__PURE__ */ jsxs(Fragment, { children: [render(trigger, {
1305
+ [triggerRefName]: triggerRef,
1306
+ active,
1307
+ ...passHandlers ? {
1308
+ showContent,
1309
+ hideContent
1310
+ } : {}
1311
+ }), IS_BROWSER && active && /* @__PURE__ */ jsx(Portal_default, {
1312
+ DOMLocation,
1313
+ children: /* @__PURE__ */ jsx(Provider, {
1314
+ ...ctx,
1315
+ children: render(children, {
1316
+ [contentRefName]: contentRef,
1317
+ active,
1318
+ align,
1319
+ alignX,
1320
+ alignY,
1321
+ ...passHandlers ? {
1322
+ showContent,
1323
+ hideContent
1324
+ } : {}
1325
+ })
1326
+ })
1327
+ })] });
1224
1328
  };
1225
1329
  const name$2 = `${PKG_NAME}/Overlay`;
1226
- Component$2.displayName = name$2;
1227
- Component$2.pkgName = PKG_NAME;
1228
- Component$2.VITUS_LABS__COMPONENT = name$2;
1330
+ Component$3.displayName = name$2;
1331
+ Component$3.pkgName = PKG_NAME;
1332
+ Component$3.VITUS_LABS__COMPONENT = name$2;
1333
+
1334
+ //#endregion
1335
+ //#region src/Overlay/index.ts
1336
+ var Overlay_default = Component$3;
1229
1337
 
1338
+ //#endregion
1339
+ //#region src/Text/styled.ts
1340
+ /**
1341
+ * Styled text primitive that inherits color, font-weight, and line-height
1342
+ * from its parent so it blends seamlessly into any context. Additional
1343
+ * styles can be injected via the responsive `extraStyles` prop processed
1344
+ * through makeItResponsive.
1345
+ */
1230
1346
  const { styled, css, textComponent } = config;
1231
- const styles = ({ css, theme: t }) => css `
1347
+ const styles = ({ css, theme: t }) => css`
1232
1348
  ${t.extraStyles && extendCss(t.extraStyles)};
1233
1349
  `;
1234
- var Styled = styled(textComponent) `
1350
+ var styled_default = styled(textComponent)`
1235
1351
  color: inherit;
1236
1352
  font-weight: inherit;
1237
1353
  line-height: 1;
1238
1354
 
1239
1355
  ${makeItResponsive({
1240
- key: '$text',
1241
- styles,
1242
- css,
1243
- normalize: false,
1356
+ key: "$text",
1357
+ styles,
1358
+ css,
1359
+ normalize: false
1244
1360
  })};
1245
1361
  `;
1246
1362
 
1247
- const Component$1 = forwardRef(({ paragraph, label, children, tag, css, ...props }, ref) => {
1248
- const renderContent = (as = undefined) => (React.createElement(Styled, { ref: ref, as: as, "$text": { extraStyles: css }, ...props }, children ?? label));
1249
- let finalTag;
1250
- {
1251
- if (paragraph)
1252
- finalTag = 'p';
1253
- else {
1254
- finalTag = tag;
1255
- }
1256
- }
1257
- return renderContent(finalTag);
1363
+ //#endregion
1364
+ //#region src/Text/component.tsx
1365
+ const Component$2 = forwardRef(({ paragraph, label, children, tag, css, ...props }, ref) => {
1366
+ const renderContent = (as = void 0) => /* @__PURE__ */ jsx(styled_default, {
1367
+ ref,
1368
+ as,
1369
+ $text: { extraStyles: css },
1370
+ ...props,
1371
+ children: children ?? label
1372
+ });
1373
+ let finalTag;
1374
+ if (paragraph) finalTag = "p";
1375
+ else finalTag = tag;
1376
+ return renderContent(finalTag);
1258
1377
  });
1259
- // ----------------------------------------------
1260
- // DEFINE STATICS
1261
- // ----------------------------------------------
1262
1378
  const name$1 = `${PKG_NAME}/Text`;
1263
- Component$1.displayName = name$1;
1264
- Component$1.pkgName = PKG_NAME;
1265
- Component$1.VITUS_LABS__COMPONENT = name$1;
1266
- Component$1.isText = true;
1379
+ Component$2.displayName = name$1;
1380
+ Component$2.pkgName = PKG_NAME;
1381
+ Component$2.VITUS_LABS__COMPONENT = name$1;
1382
+ Component$2.isText = true;
1383
+
1384
+ //#endregion
1385
+ //#region src/Text/index.ts
1386
+ var Text_default = Component$2;
1267
1387
 
1268
- const Component = ({ children, className, style }) => {
1269
- const mergedClasses = useMemo(() => (Array.isArray(className) ? className.join(' ') : className), [className]);
1270
- const finalProps = {};
1271
- if (style)
1272
- finalProps.style = style;
1273
- if (mergedClasses)
1274
- finalProps.className = mergedClasses;
1275
- return render(children, finalProps);
1388
+ //#endregion
1389
+ //#region src/Util/component.tsx
1390
+ /**
1391
+ * Utility wrapper that injects className and/or style props into its
1392
+ * children without adding any DOM nodes of its own. Uses the core `render`
1393
+ * helper to clone children with the merged props.
1394
+ */
1395
+ const Component$1 = ({ children, className, style }) => {
1396
+ const mergedClasses = useMemo(() => Array.isArray(className) ? className.join(" ") : className, [className]);
1397
+ const finalProps = {};
1398
+ if (style) finalProps.style = style;
1399
+ if (mergedClasses) finalProps.className = mergedClasses;
1400
+ return render(children, finalProps);
1276
1401
  };
1277
1402
  const name = `${PKG_NAME}/Util`;
1278
- Component.displayName = name;
1279
- Component.pkgName = PKG_NAME;
1280
- Component.VITUS_LABS__COMPONENT = name;
1403
+ Component$1.displayName = name;
1404
+ Component$1.pkgName = PKG_NAME;
1405
+ Component$1.VITUS_LABS__COMPONENT = name;
1406
+
1407
+ //#endregion
1408
+ //#region src/Util/index.ts
1409
+ var Util_default = Component$1;
1281
1410
 
1282
- export { Component$7 as Element, Component$5 as List, Component$2 as Overlay, Component$3 as OverlayProvider, Component$4 as Portal, Component$1 as Text, Component as Util, useOverlay, component as withActiveState, withEqualBeforeAfter as withEqualSizeBeforeAfter };
1283
- //# sourceMappingURL=index.js.map
1411
+ //#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 };
1413
+ //# sourceMappingURL=index.js.map