@vitus-labs/elements 2.0.0-alpha.8 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Provider, alignContent, extendCss, makeItResponsive, value } from "@vitus-labs/unistyle";
2
2
  import { config, context, isEmpty, omit, pick, render, throttle } from "@vitus-labs/core";
3
- import { Children, createContext, forwardRef, memo, useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
3
+ import { Children, createContext, forwardRef, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { isFragment } from "react-is";
6
6
  import { createPortal } from "react-dom";
@@ -21,7 +21,7 @@ const IS_DEVELOPMENT = process.env.NODE_ENV !== "production";
21
21
  * equalCols flex distribution. The "content" slot gets `flex: 1` to
22
22
  * fill remaining space between before and after.
23
23
  */
24
- const { styled: styled$2, css: css$2, component: component$2 } = config;
24
+ const { styled: styled$2, css: css$2, component: component$1 } = config;
25
25
  const equalColsCSS = `
26
26
  flex: 1;
27
27
  `;
@@ -67,7 +67,7 @@ const styles$2 = ({ css, theme: t, rootSize }) => css`
67
67
 
68
68
  ${t.extraStyles && extendCss(t.extraStyles)};
69
69
  `;
70
- const StyledComponent = styled$2(component$2)`
70
+ const StyledComponent = styled$2(component$1)`
71
71
  ${`box-sizing: border-box;`};
72
72
 
73
73
  display: flex;
@@ -86,11 +86,22 @@ const StyledComponent = styled$2(component$2)`
86
86
 
87
87
  //#endregion
88
88
  //#region src/helpers/Content/component.tsx
89
- const Component$9 = ({ contentType, tag, parentDirection, direction, alignX, alignY, equalCols, gap, extendCss, ...props }) => {
89
+ /**
90
+ * Memoized content area used inside Element to render one of the three
91
+ * layout slots (before, content, after). Passes alignment, direction,
92
+ * gap, and equalCols styling props to the underlying styled component.
93
+ * Adds a `data-vl-element` attribute in development for debugging.
94
+ *
95
+ * Children are passed as raw content and rendered inside the memo boundary
96
+ * via core `render()` — this lets React.memo skip re-renders when the
97
+ * content reference is stable (common for component-type or string content).
98
+ */
99
+ const Component$9 = ({ contentType, tag, parentDirection, direction, alignX, alignY, equalCols, gap, extendCss, children, ...props }) => {
100
+ const debugProps = IS_DEVELOPMENT ? { "data-vl-element": contentType } : {};
90
101
  return /* @__PURE__ */ jsx(StyledComponent, {
91
102
  as: tag,
92
103
  $contentType: contentType,
93
- $element: {
104
+ $element: useMemo(() => ({
94
105
  contentType,
95
106
  parentDirection,
96
107
  direction,
@@ -99,16 +110,26 @@ const Component$9 = ({ contentType, tag, parentDirection, direction, alignX, ali
99
110
  equalCols,
100
111
  gap,
101
112
  extraStyles: extendCss
102
- },
103
- ...IS_DEVELOPMENT ? { "data-vl-element": contentType } : {},
104
- ...props
113
+ }), [
114
+ contentType,
115
+ parentDirection,
116
+ direction,
117
+ alignX,
118
+ alignY,
119
+ equalCols,
120
+ gap,
121
+ extendCss
122
+ ]),
123
+ ...debugProps,
124
+ ...props,
125
+ children: render(children)
105
126
  });
106
127
  };
107
- var component_default = memo(Component$9);
128
+ var component_default$1 = memo(Component$9);
108
129
 
109
130
  //#endregion
110
131
  //#region src/helpers/Content/index.ts
111
- var Content_default = component_default;
132
+ var Content_default = component_default$1;
112
133
 
113
134
  //#endregion
114
135
  //#region src/helpers/Wrapper/styled.ts
@@ -119,7 +140,7 @@ var Content_default = component_default;
119
140
  * split flex behavior across two DOM nodes for button/fieldset/legend
120
141
  * elements where a single flex container is insufficient.
121
142
  */
122
- const { styled: styled$1, css: css$1, component: component$1 } = config;
143
+ const { styled: styled$1, css: css$1, component } = config;
123
144
  const childFixCSS = `
124
145
  display: flex;
125
146
  flex: 1;
@@ -129,14 +150,12 @@ const childFixCSS = `
129
150
  const parentFixCSS = `
130
151
  flex-direction: column;
131
152
  `;
132
- const parentFixBlockCSS = `
133
- width: 100%;
134
- `;
135
153
  const fullHeightCSS = `
136
154
  height: 100%;
137
155
  `;
138
156
  const blockCSS = `
139
157
  align-self: stretch;
158
+ width: 100%;
140
159
  `;
141
160
  const childFixPosition = (isBlock) => `display: ${isBlock ? "flex" : "inline-flex"};`;
142
161
  const styles$1 = ({ theme: t, css }) => css`
@@ -149,15 +168,15 @@ const styles$1 = ({ theme: t, css }) => css`
149
168
  })};
150
169
 
151
170
  ${t.block && blockCSS};
171
+ ${t.alignY === "block" && t.block && fullHeightCSS};
152
172
 
153
173
  ${!t.childFix && childFixPosition(t.block)};
154
- ${t.parentFix && t.block && parentFixBlockCSS};
155
174
  ${t.parentFix && parentFixCSS};
156
175
 
157
176
  ${t.extraStyles && extendCss(t.extraStyles)};
158
177
  `;
159
178
  const platformCSS = `box-sizing: border-box;`;
160
- var styled_default$1 = styled$1(component$1)`
179
+ var styled_default$1 = styled$1(component)`
161
180
  position: relative;
162
181
  ${platformCSS};
163
182
 
@@ -208,36 +227,52 @@ const Component$8 = forwardRef(({ children, tag, block, extendCss, direction, al
208
227
  ref,
209
228
  as: tag
210
229
  };
211
- if (!(!props.dangerouslySetInnerHTML && isWebFixNeeded(tag)) || false) return /* @__PURE__ */ jsx(styled_default$1, {
230
+ const needsFix = !props.dangerouslySetInnerHTML && isWebFixNeeded(tag);
231
+ const normalElement = useMemo(() => ({
232
+ block,
233
+ direction,
234
+ alignX,
235
+ alignY,
236
+ equalCols,
237
+ extraStyles: extendCss
238
+ }), [
239
+ block,
240
+ direction,
241
+ alignX,
242
+ alignY,
243
+ equalCols,
244
+ extendCss
245
+ ]);
246
+ const parentFixElement = useMemo(() => ({
247
+ parentFix: true,
248
+ block,
249
+ extraStyles: extendCss
250
+ }), [block, extendCss]);
251
+ const childFixElement = useMemo(() => ({
252
+ childFix: true,
253
+ direction,
254
+ alignX,
255
+ alignY,
256
+ equalCols
257
+ }), [
258
+ direction,
259
+ alignX,
260
+ alignY,
261
+ equalCols
262
+ ]);
263
+ if (!needsFix || false) return /* @__PURE__ */ jsx(styled_default$1, {
212
264
  ...COMMON_PROPS,
213
- $element: {
214
- block,
215
- direction,
216
- alignX,
217
- alignY,
218
- equalCols,
219
- extraStyles: extendCss
220
- },
265
+ $element: normalElement,
221
266
  children
222
267
  });
223
268
  const asTag = isInline ? "span" : "div";
224
269
  return /* @__PURE__ */ jsx(styled_default$1, {
225
270
  ...COMMON_PROPS,
226
- $element: {
227
- parentFix: true,
228
- block,
229
- extraStyles: extendCss
230
- },
271
+ $element: parentFixElement,
231
272
  children: /* @__PURE__ */ jsx(styled_default$1, {
232
273
  as: asTag,
233
274
  $childFix: true,
234
- $element: {
235
- childFix: true,
236
- direction,
237
- alignX,
238
- alignY,
239
- equalCols
240
- },
275
+ $element: childFixElement,
241
276
  children
242
277
  })
243
278
  });
@@ -332,11 +367,26 @@ const getShouldBeEmpty = (tag) => {
332
367
  * like void elements (input, img) and inline elements (span, a) by
333
368
  * skipping children or switching sub-tags accordingly.
334
369
  */
370
+ const equalize = (el, direction) => {
371
+ const beforeEl = el.firstElementChild;
372
+ const afterEl = el.lastElementChild;
373
+ if (beforeEl && afterEl && beforeEl !== afterEl) {
374
+ const type = direction === "rows" ? "height" : "width";
375
+ const prop = type === "height" ? "offsetHeight" : "offsetWidth";
376
+ const beforeSize = beforeEl[prop];
377
+ const afterSize = afterEl[prop];
378
+ if (Number.isInteger(beforeSize) && Number.isInteger(afterSize)) {
379
+ const maxSize = `${Math.max(beforeSize, afterSize)}px`;
380
+ beforeEl.style[type] = maxSize;
381
+ afterEl.style[type] = maxSize;
382
+ }
383
+ }
384
+ };
335
385
  const defaultDirection = "inline";
336
386
  const defaultContentDirection = "rows";
337
387
  const defaultAlignX = "left";
338
388
  const defaultAlignY = "center";
339
- const Component$7 = forwardRef(({ innerRef, tag, label, content, children, beforeContent, afterContent, block, equalCols, gap, direction, alignX = defaultAlignX, alignY = defaultAlignY, css, contentCss, beforeContentCss, afterContentCss, contentDirection = defaultContentDirection, contentAlignX = defaultAlignX, contentAlignY = defaultAlignY, beforeContentDirection = defaultDirection, beforeContentAlignX = defaultAlignX, beforeContentAlignY = defaultAlignY, afterContentDirection = defaultDirection, afterContentAlignX = defaultAlignX, afterContentAlignY = defaultAlignY, ...props }, ref) => {
389
+ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, beforeContent, afterContent, equalBeforeAfter, block, equalCols, gap, direction, alignX = defaultAlignX, alignY = defaultAlignY, css, contentCss, beforeContentCss, afterContentCss, contentDirection = defaultContentDirection, contentAlignX = defaultAlignX, contentAlignY = defaultAlignY, beforeContentDirection = defaultDirection, beforeContentAlignX = defaultAlignX, beforeContentAlignY = defaultAlignY, afterContentDirection = defaultDirection, afterContentAlignX = defaultAlignX, afterContentAlignY = defaultAlignY, ...props }, ref) => {
340
390
  const shouldBeEmpty = !!props.dangerouslySetInnerHTML || getShouldBeEmpty(tag);
341
391
  const isSimpleElement = !beforeContent && !afterContent;
342
392
  const CHILDREN = children ?? content ?? label;
@@ -366,8 +416,24 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
366
416
  alignY,
367
417
  direction
368
418
  ]);
419
+ const equalizeRef = useRef(null);
420
+ const externalRef = ref ?? innerRef;
421
+ const mergedRef = useCallback((node) => {
422
+ equalizeRef.current = node;
423
+ if (typeof externalRef === "function") externalRef(node);
424
+ else if (externalRef != null) externalRef.current = node;
425
+ }, [externalRef]);
426
+ useLayoutEffect(() => {
427
+ if (!equalBeforeAfter || !beforeContent || !afterContent) return;
428
+ if (equalizeRef.current) equalize(equalizeRef.current, direction);
429
+ }, [
430
+ equalBeforeAfter,
431
+ beforeContent,
432
+ afterContent,
433
+ direction
434
+ ]);
369
435
  const WRAPPER_PROPS = {
370
- ref: ref ?? innerRef,
436
+ ref: mergedRef,
371
437
  extendCss: css,
372
438
  tag,
373
439
  block,
@@ -380,7 +446,6 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
380
446
  ...props,
381
447
  ...WRAPPER_PROPS
382
448
  });
383
- const contentRenderOutput = render(CHILDREN);
384
449
  return /* @__PURE__ */ jsxs(Wrapper_default, {
385
450
  ...props,
386
451
  ...WRAPPER_PROPS,
@@ -396,9 +461,9 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
396
461
  alignY: beforeContentAlignY,
397
462
  equalCols,
398
463
  gap,
399
- children: render(beforeContent)
464
+ children: beforeContent
400
465
  }),
401
- isSimpleElement ? contentRenderOutput : /* @__PURE__ */ jsx(Content_default, {
466
+ isSimpleElement ? render(CHILDREN) : /* @__PURE__ */ jsx(Content_default, {
402
467
  tag: SUB_TAG,
403
468
  contentType: "content",
404
469
  parentDirection: wrapperDirection,
@@ -407,7 +472,7 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
407
472
  alignX: contentAlignX,
408
473
  alignY: contentAlignY,
409
474
  equalCols,
410
- children: contentRenderOutput
475
+ children: CHILDREN
411
476
  }),
412
477
  afterContent && /* @__PURE__ */ jsx(Content_default, {
413
478
  tag: SUB_TAG,
@@ -419,7 +484,7 @@ const Component$7 = forwardRef(({ innerRef, tag, label, content, children, befor
419
484
  alignY: afterContentAlignY,
420
485
  equalCols,
421
486
  gap,
422
- children: render(afterContent)
487
+ children: afterContent
423
488
  })
424
489
  ]
425
490
  });
@@ -429,53 +494,6 @@ Component$7.displayName = name$5;
429
494
  Component$7.pkgName = PKG_NAME;
430
495
  Component$7.VITUS_LABS__COMPONENT = name$5;
431
496
 
432
- //#endregion
433
- //#region src/Element/withEqualSizeBeforeAfter.tsx
434
- /**
435
- * HOC that equalizes the dimensions of beforeContent and afterContent areas.
436
- * After render, it measures both DOM nodes via useLayoutEffect and sets the
437
- * larger dimension on both so they match. Uses width for inline direction
438
- * and height for rows direction. This is useful for centering the main
439
- * content when before/after slots have different intrinsic sizes.
440
- */
441
- const types = {
442
- height: "offsetHeight",
443
- width: "offsetWidth"
444
- };
445
- const equalize = (beforeEl, afterEl, type) => {
446
- const prop = types[type];
447
- const beforeSize = beforeEl[prop];
448
- const afterSize = afterEl[prop];
449
- if (Number.isInteger(beforeSize) && Number.isInteger(afterSize)) {
450
- const maxSize = `${Math.max(beforeSize, afterSize)}px`;
451
- beforeEl.style[type] = maxSize;
452
- afterEl.style[type] = maxSize;
453
- }
454
- };
455
- const withEqualBeforeAfter = (WrappedComponent) => {
456
- const displayName = WrappedComponent.displayName ?? WrappedComponent.name ?? "Component";
457
- const Enhanced = ({ equalBeforeAfter, direction, afterContent, beforeContent, ref, ...rest }) => {
458
- const internalRef = useRef(null);
459
- useImperativeHandle(ref, () => internalRef.current);
460
- useLayoutEffect(() => {
461
- if (!equalBeforeAfter || !beforeContent || !afterContent) return;
462
- if (!internalRef.current) return;
463
- const el = internalRef.current;
464
- const beforeEl = el.firstElementChild;
465
- const afterEl = el.lastElementChild;
466
- if (beforeEl && afterEl && beforeEl !== afterEl) equalize(beforeEl, afterEl, direction === "rows" ? "height" : "width");
467
- });
468
- return /* @__PURE__ */ jsx(WrappedComponent, {
469
- ...rest,
470
- afterContent,
471
- beforeContent,
472
- ref: internalRef
473
- });
474
- };
475
- Enhanced.displayName = `withEqualSizeBeforeAfter(${displayName})`;
476
- return Enhanced;
477
- };
478
-
479
497
  //#endregion
480
498
  //#region src/Element/index.ts
481
499
  var Element_default = Component$7;
@@ -633,12 +651,14 @@ const Component$6 = (props) => {
633
651
  };
634
652
  return renderItems();
635
653
  };
636
- Component$6.isIterator = true;
637
- Component$6.RESERVED_PROPS = RESERVED_PROPS;
654
+ var component_default = Object.assign(memo(Component$6), {
655
+ isIterator: true,
656
+ RESERVED_PROPS
657
+ });
638
658
 
639
659
  //#endregion
640
660
  //#region src/helpers/Iterator/index.ts
641
- var Iterator_default = Component$6;
661
+ var Iterator_default = component_default;
642
662
 
643
663
  //#endregion
644
664
  //#region src/List/component.tsx
@@ -663,108 +683,6 @@ Component$5.displayName = name$4;
663
683
  Component$5.pkgName = PKG_NAME;
664
684
  Component$5.VITUS_LABS__COMPONENT = name$4;
665
685
 
666
- //#endregion
667
- //#region src/List/withActiveState.tsx
668
- /**
669
- * HOC that adds single or multi selection state management to a list component.
670
- * Tracks which items are active via a scalar key (single mode) or a Map of
671
- * key-to-boolean entries (multi mode). Injects `itemProps` callback that
672
- * provides each item with `active`, `handleItemActive`, `toggleItemActive`,
673
- * and other selection helpers. Supports `activeItemRequired` to prevent
674
- * deselecting the last active item.
675
- */
676
- const RESERVED_KEYS = [
677
- "type",
678
- "activeItems",
679
- "itemProps",
680
- "activeItemRequired"
681
- ];
682
- const component = (WrappedComponent) => {
683
- const displayName = WrappedComponent.displayName || WrappedComponent.name || "Component";
684
- const Enhanced = (props) => {
685
- const { type = "single", activeItemRequired, activeItems, itemProps = {}, ...rest } = props;
686
- const initActiveItems = () => {
687
- if (type === "single") {
688
- if (!Array.isArray(activeItems)) return activeItems;
689
- } else if (type === "multi") {
690
- const activeItemsHelper = Array.isArray(activeItems) ? activeItems : [activeItems];
691
- return new Map(activeItemsHelper.map((id) => [id, true]));
692
- }
693
- };
694
- const [innerActiveItems, setActiveItems] = useState(initActiveItems());
695
- const countActiveItems = (data) => {
696
- let result = 0;
697
- data.forEach((value) => {
698
- if (value) result += 1;
699
- });
700
- return result;
701
- };
702
- const updateItemState = (key) => {
703
- if (type === "single") setActiveItems((prevState) => {
704
- if (activeItemRequired) return key;
705
- if (prevState === key) return void 0;
706
- return key;
707
- });
708
- else if (type === "multi") setActiveItems((prevState) => {
709
- const activeItems = new Map(prevState);
710
- if (activeItemRequired && activeItems.get(key) && countActiveItems(activeItems) === 1) return activeItems;
711
- activeItems.set(key, !activeItems.get(key));
712
- return activeItems;
713
- });
714
- else setActiveItems(void 0);
715
- };
716
- const handleItemActive = (key) => {
717
- updateItemState(key);
718
- };
719
- const updateAllItemsState = (status) => {
720
- if (!status) setActiveItems(/* @__PURE__ */ new Map());
721
- };
722
- const setItemActive = (key) => {
723
- updateItemState(key);
724
- };
725
- const unsetItemActive = (key) => {
726
- updateItemState(key);
727
- };
728
- const toggleItemActive = (key) => {
729
- updateItemState(key);
730
- };
731
- const unsetAllItemsActive = () => {
732
- updateAllItemsState(false);
733
- };
734
- const isItemActive = (key) => {
735
- if (!innerActiveItems) return false;
736
- if (type === "single") return innerActiveItems === key;
737
- if (type === "multi" && innerActiveItems instanceof Map) return !!innerActiveItems.get(key);
738
- return false;
739
- };
740
- const attachMultipleProps = { unsetAllItemsActive };
741
- const attachItemProps = (props) => {
742
- const { key } = props;
743
- return {
744
- ...typeof itemProps === "object" ? itemProps : itemProps(props),
745
- active: isItemActive(key),
746
- handleItemActive: () => handleItemActive(key),
747
- setItemActive,
748
- unsetItemActive,
749
- toggleItemActive,
750
- ...type === "multi" ? attachMultipleProps : {}
751
- };
752
- };
753
- useEffect(() => {
754
- if (type === "single" && Array.isArray(activeItems)) {
755
- if (process.env.NODE_ENV !== "production") console.warn("[@vitus-labs/elements] List/withActiveState: `activeItems` was passed as an array but `type` is \"single\". In single selection mode, `activeItems` should be a single key (string | number). The array value will be ignored.");
756
- }
757
- }, [type, activeItems]);
758
- return /* @__PURE__ */ jsx(WrappedComponent, {
759
- ...rest,
760
- itemProps: attachItemProps
761
- });
762
- };
763
- Enhanced.RESERVED_KEYS = RESERVED_KEYS;
764
- Enhanced.displayName = `@vitus-labs/elements/List/withActiveState(${displayName})`;
765
- return Enhanced;
766
- };
767
-
768
686
  //#endregion
769
687
  //#region src/List/index.ts
770
688
  var List_default = Component$5;
@@ -1163,6 +1081,8 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
1163
1081
  window.addEventListener("resize", handleContentPosition);
1164
1082
  window.addEventListener("scroll", onScroll, { passive: true });
1165
1083
  return () => {
1084
+ handleContentPosition.cancel();
1085
+ handleVisibility.cancel();
1166
1086
  if (shouldSetOverflow) document.body.style.overflow = "";
1167
1087
  window.removeEventListener("resize", handleContentPosition);
1168
1088
  window.removeEventListener("scroll", onScroll);
@@ -1269,10 +1189,8 @@ const useOverlay = ({ isOpen = false, openOn = "click", closeOn = "click", type
1269
1189
  return {
1270
1190
  triggerRef,
1271
1191
  contentRef: useCallback((node) => {
1272
- if (node) {
1273
- contentRef.current = node;
1274
- setContentLoaded(true);
1275
- }
1192
+ contentRef.current = node;
1193
+ setContentLoaded(!!node);
1276
1194
  }, []),
1277
1195
  active,
1278
1196
  align,
@@ -1348,9 +1266,11 @@ const styles = ({ css, theme: t }) => css`
1348
1266
  ${t.extraStyles && extendCss(t.extraStyles)};
1349
1267
  `;
1350
1268
  var styled_default = styled(textComponent)`
1351
- color: inherit;
1352
- font-weight: inherit;
1353
- line-height: 1;
1269
+ ${css`
1270
+ color: inherit;
1271
+ font-weight: inherit;
1272
+ line-height: 1;
1273
+ `};
1354
1274
 
1355
1275
  ${makeItResponsive({
1356
1276
  key: "$text",
@@ -1409,5 +1329,5 @@ Component$1.VITUS_LABS__COMPONENT = name;
1409
1329
  var Util_default = Component$1;
1410
1330
 
1411
1331
  //#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 };
1332
+ export { Element_default as Element, List_default as List, Overlay_default as Overlay, Component as OverlayProvider, Portal_default as Portal, Provider, Text_default as Text, Util_default as Util, useOverlay };
1413
1333
  //# sourceMappingURL=index.js.map