@ecohouse/ui 0.1.53 → 0.1.54

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/dist/index.cjs CHANGED
@@ -27561,7 +27561,7 @@ var require_react_dom_development = __commonJS({
27561
27561
  return root2;
27562
27562
  }
27563
27563
  var ReactVersion = "18.3.1";
27564
- function createPortal3(children, containerInfo, implementation) {
27564
+ function createPortal4(children, containerInfo, implementation) {
27565
27565
  var key = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : null;
27566
27566
  {
27567
27567
  checkKeyStringCoercion(key);
@@ -28417,7 +28417,7 @@ var require_react_dom_development = __commonJS({
28417
28417
  if (!isValidContainer(container)) {
28418
28418
  throw new Error("Target container is not a DOM element.");
28419
28419
  }
28420
- return createPortal3(children, container, null, key);
28420
+ return createPortal4(children, container, null, key);
28421
28421
  }
28422
28422
  function renderSubtreeIntoContainer(parentComponent, element, containerNode, callback) {
28423
28423
  return unstable_renderSubtreeIntoContainer(parentComponent, element, containerNode, callback);
@@ -36214,6 +36214,9 @@ var styles32 = reactNative.StyleSheet.create({
36214
36214
  color: colors.red
36215
36215
  }
36216
36216
  });
36217
+
36218
+ // src/components/Select/Select.tsx
36219
+ var import_react_dom3 = __toESM(require_react_dom());
36217
36220
  var DEFAULT_WIDTH4 = 308;
36218
36221
  var TRIGGER_HEIGHT2 = 44;
36219
36222
  var TRIGGER_RADIUS2 = 60;
@@ -36223,6 +36226,9 @@ var ITEM_RADIUS2 = 12;
36223
36226
  var ICON_SIZE13 = 20;
36224
36227
  var CHIP_GAP = 6;
36225
36228
  var FALLBACK_ELLIPSIS_WIDTH = 20;
36229
+ var WEB_MENU_Z_INDEX = 600;
36230
+ var WEB_VIEWPORT_PADDING = 8;
36231
+ var WEB_MENU_GAP = 8;
36226
36232
  function itemBackground(state) {
36227
36233
  switch (state) {
36228
36234
  case "hover":
@@ -36369,6 +36375,7 @@ var Select = react.forwardRef(
36369
36375
  const valueProp = props.value;
36370
36376
  const rootRef = react.useRef(null);
36371
36377
  const triggerRef = react.useRef(null);
36378
+ const menuRef = react.useRef(null);
36372
36379
  const searchInputRef = react.useRef(null);
36373
36380
  const setRootRef = react.useMemo(() => mergeRefs(rootRef, forwardedRef), [forwardedRef]);
36374
36381
  const isOpenControlled = typeof openProp === "boolean";
@@ -36384,6 +36391,7 @@ var Select = react.forwardRef(
36384
36391
  const [hoveredValue, setHoveredValue] = react.useState(null);
36385
36392
  const [menuAlignEnd, setMenuAlignEnd] = react.useState(false);
36386
36393
  const [menuPlaceAbove, setMenuPlaceAbove] = react.useState(false);
36394
+ const [webMenuPosition, setWebMenuPosition] = react.useState(null);
36387
36395
  const selectedValues = react.useMemo(() => {
36388
36396
  const current = isValueControlled ? valueProp : multiple ? uncontrolledMultiple : uncontrolledSingle;
36389
36397
  return toArray(current);
@@ -36437,6 +36445,7 @@ var Select = react.forwardRef(
36437
36445
  setHoveredValue(null);
36438
36446
  setMenuAlignEnd(false);
36439
36447
  setMenuPlaceAbove(false);
36448
+ setWebMenuPosition(null);
36440
36449
  onSearchChange?.("");
36441
36450
  }
36442
36451
  },
@@ -36448,15 +36457,18 @@ var Select = react.forwardRef(
36448
36457
  const trigger = triggerRef.current;
36449
36458
  const rect = trigger?.getBoundingClientRect?.();
36450
36459
  if (!rect) return;
36451
- const viewportPadding = 8;
36452
- const gap = 8;
36453
36460
  const menuWidth = Math.max(rect.width, resolvedMenuWidth ?? 0);
36454
- const overflowsRight = rect.left + menuWidth > window.innerWidth - viewportPadding;
36461
+ const overflowsRight = rect.left + menuWidth > window.innerWidth - WEB_VIEWPORT_PADDING;
36455
36462
  setMenuAlignEnd(overflowsRight);
36456
- const spaceBelow = window.innerHeight - viewportPadding - rect.bottom;
36457
- const spaceAbove = rect.top - viewportPadding;
36458
- const needsAbove = estimatedMenuHeight + gap > spaceBelow && spaceAbove > spaceBelow;
36463
+ const spaceBelow = window.innerHeight - WEB_VIEWPORT_PADDING - rect.bottom;
36464
+ const spaceAbove = rect.top - WEB_VIEWPORT_PADDING;
36465
+ const needsAbove = estimatedMenuHeight + WEB_MENU_GAP > spaceBelow && spaceAbove > spaceBelow;
36459
36466
  setMenuPlaceAbove(needsAbove);
36467
+ const available = Math.max(0, (needsAbove ? spaceAbove : spaceBelow) - WEB_MENU_GAP);
36468
+ const maxHeight = Math.min(estimatedMenuHeight, Math.max(available, TRIGGER_HEIGHT2));
36469
+ const left = overflowsRight ? Math.max(WEB_VIEWPORT_PADDING, rect.right - menuWidth) : Math.min(rect.left, window.innerWidth - WEB_VIEWPORT_PADDING - menuWidth);
36470
+ const top = needsAbove ? Math.max(WEB_VIEWPORT_PADDING, rect.top - WEB_MENU_GAP - maxHeight) : rect.bottom + WEB_MENU_GAP;
36471
+ setWebMenuPosition({ top, left, width: menuWidth, maxHeight });
36460
36472
  };
36461
36473
  updateMenuPlacement();
36462
36474
  window.addEventListener("resize", updateMenuPlacement);
@@ -36473,11 +36485,12 @@ var Select = react.forwardRef(
36473
36485
  react.useEffect(() => {
36474
36486
  if (reactNative.Platform.OS !== "web" || !isOpen) return;
36475
36487
  const handlePointerDown = (event) => {
36476
- const root = rootRef.current;
36477
36488
  const target = event.target;
36478
- if (root?.contains && target instanceof Node && !root.contains(target)) {
36479
- setOpen(false);
36480
- }
36489
+ if (!(target instanceof Node)) return;
36490
+ const root = rootRef.current;
36491
+ const menu = menuRef.current;
36492
+ if (root?.contains?.(target) || menu?.contains?.(target)) return;
36493
+ setOpen(false);
36481
36494
  };
36482
36495
  document.addEventListener("mousedown", handlePointerDown);
36483
36496
  document.addEventListener("touchstart", handlePointerDown);
@@ -36591,7 +36604,7 @@ var Select = react.forwardRef(
36591
36604
  ]);
36592
36605
  const leftIconNode = renderStatefulIcon(leftIcon, UserIcon, {
36593
36606
  size: ICON_SIZE13,
36594
- color: error ? colors.red : colors.primary
36607
+ color: error ? colors.red : isOpen ? colors.primary : colors.grey100
36595
36608
  });
36596
36609
  const hasLeftIcon = showLeftIcon || leftIcon != null;
36597
36610
  const triggerBorderColor = error ? colors.redMuted : isOpen ? colors.primaryMuted : colors.grey700;
@@ -36600,6 +36613,95 @@ var Select = react.forwardRef(
36600
36613
  const hintColor = error ? colors.red : colors.grey100;
36601
36614
  const singleLabel = selectedOptions[0]?.label ?? placeholder;
36602
36615
  const resolvedAccessibilityLabel = accessibilityLabel ?? (typeof label === "string" ? label : void 0);
36616
+ const optionListMaxHeight = reactNative.Platform.OS === "web" && webMenuPosition != null ? Math.max(43, webMenuPosition.maxHeight - (24 + (showSearch ? 48 : 0))) : resolvedOptionListMaxHeight;
36617
+ const menuNode = isOpen ? /* @__PURE__ */ jsxRuntime.jsxs(
36618
+ reactNative.View,
36619
+ {
36620
+ ref: menuRef,
36621
+ style: [
36622
+ styles33.menu,
36623
+ reactNative.Platform.OS === "web" && webMenuPosition != null ? {
36624
+ position: "fixed",
36625
+ top: webMenuPosition.top,
36626
+ left: webMenuPosition.left,
36627
+ width: webMenuPosition.width,
36628
+ minWidth: webMenuPosition.width,
36629
+ maxHeight: webMenuPosition.maxHeight,
36630
+ zIndex: WEB_MENU_Z_INDEX,
36631
+ marginTop: 0,
36632
+ marginBottom: 0
36633
+ } : reactNative.Platform.OS === "web" ? { position: "fixed", opacity: 0, pointerEvents: "none" } : [
36634
+ menuAlignEnd ? styles33.menuAlignEnd : styles33.menuAlignStart,
36635
+ menuPlaceAbove ? styles33.menuAbove : styles33.menuBelow
36636
+ ],
36637
+ menuStyle
36638
+ ],
36639
+ children: [
36640
+ showSearch ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles33.searchField, children: [
36641
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles33.iconSlot, children: /* @__PURE__ */ jsxRuntime.jsx(SearchIcon, { size: ICON_SIZE13, color: colors.grey100 }) }),
36642
+ /* @__PURE__ */ jsxRuntime.jsx(
36643
+ reactNative.TextInput,
36644
+ {
36645
+ ref: searchInputRef,
36646
+ value: searchQuery,
36647
+ onChangeText: handleSearchChange,
36648
+ editable: !disabled,
36649
+ placeholder: searchPlaceholder,
36650
+ placeholderTextColor: colors.grey100,
36651
+ style: [
36652
+ styles33.searchInput,
36653
+ reactNative.Platform.OS === "web" ? styles33.searchInputWeb : null,
36654
+ reactNative.Platform.OS === "android" ? styles33.searchInputAndroid : null
36655
+ ],
36656
+ accessibilityLabel: searchPlaceholder
36657
+ }
36658
+ )
36659
+ ] }) : null,
36660
+ /* @__PURE__ */ jsxRuntime.jsxs(
36661
+ reactNative.ScrollView,
36662
+ {
36663
+ style: [styles33.optionList, { maxHeight: optionListMaxHeight }],
36664
+ contentContainerStyle: styles33.optionListContent,
36665
+ keyboardShouldPersistTaps: "handled",
36666
+ showsVerticalScrollIndicator: false,
36667
+ children: [
36668
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles33.statusRow, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles33.statusText, children: "Loading..." }) }) : filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles33.statusRow, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles33.statusText, children: emptyText }) }) : null,
36669
+ !loading && filteredOptions.map((option) => {
36670
+ const isSelected = selectedValues.includes(option.value);
36671
+ const isHovered = hoveredValue === option.value;
36672
+ const visualState = isSelected ? "selected" : isHovered ? "hover" : "default";
36673
+ return /* @__PURE__ */ jsxRuntime.jsxs(
36674
+ reactNative.Pressable,
36675
+ {
36676
+ onPress: () => handleSelect(option.value),
36677
+ disabled,
36678
+ onHoverIn: () => setHoveredValue(option.value),
36679
+ onHoverOut: () => setHoveredValue((current) => current === option.value ? null : current),
36680
+ onPressIn: () => setHoveredValue(option.value),
36681
+ onPressOut: () => setHoveredValue((current) => current === option.value ? null : current),
36682
+ accessibilityRole: multiple ? "checkbox" : "button",
36683
+ accessibilityState: { selected: isSelected, checked: isSelected, disabled },
36684
+ style: [
36685
+ styles33.item,
36686
+ {
36687
+ backgroundColor: itemBackground(visualState)
36688
+ }
36689
+ ],
36690
+ children: [
36691
+ multiple ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles33.itemCheckbox, { pointerEvents: "none" }], children: /* @__PURE__ */ jsxRuntime.jsx(Checkbox, { value: isSelected, variant: "light" }) }) : null,
36692
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles33.itemLabel, numberOfLines: 1, children: option.label })
36693
+ ]
36694
+ },
36695
+ option.value
36696
+ );
36697
+ })
36698
+ ]
36699
+ }
36700
+ )
36701
+ ]
36702
+ }
36703
+ ) : null;
36704
+ const webMenuPortal = reactNative.Platform.OS === "web" && isOpen && typeof document !== "undefined" ? (0, import_react_dom3.createPortal)(menuNode, document.body) : null;
36603
36705
  return /* @__PURE__ */ jsxRuntime.jsxs(
36604
36706
  reactNative.View,
36605
36707
  {
@@ -36640,80 +36742,7 @@ var Select = react.forwardRef(
36640
36742
  ]
36641
36743
  }
36642
36744
  ),
36643
- isOpen ? /* @__PURE__ */ jsxRuntime.jsxs(
36644
- reactNative.View,
36645
- {
36646
- style: [
36647
- styles33.menu,
36648
- menuAlignEnd ? styles33.menuAlignEnd : styles33.menuAlignStart,
36649
- menuPlaceAbove ? styles33.menuAbove : styles33.menuBelow,
36650
- menuStyle
36651
- ],
36652
- children: [
36653
- showSearch ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles33.searchField, children: [
36654
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles33.iconSlot, children: /* @__PURE__ */ jsxRuntime.jsx(SearchIcon, { size: ICON_SIZE13, color: colors.grey100 }) }),
36655
- /* @__PURE__ */ jsxRuntime.jsx(
36656
- reactNative.TextInput,
36657
- {
36658
- ref: searchInputRef,
36659
- value: searchQuery,
36660
- onChangeText: handleSearchChange,
36661
- editable: !disabled,
36662
- placeholder: searchPlaceholder,
36663
- placeholderTextColor: colors.grey100,
36664
- style: [
36665
- styles33.searchInput,
36666
- reactNative.Platform.OS === "web" ? styles33.searchInputWeb : null,
36667
- reactNative.Platform.OS === "android" ? styles33.searchInputAndroid : null
36668
- ],
36669
- accessibilityLabel: searchPlaceholder
36670
- }
36671
- )
36672
- ] }) : null,
36673
- /* @__PURE__ */ jsxRuntime.jsxs(
36674
- reactNative.ScrollView,
36675
- {
36676
- style: [styles33.optionList, { maxHeight: resolvedOptionListMaxHeight }],
36677
- contentContainerStyle: styles33.optionListContent,
36678
- keyboardShouldPersistTaps: "handled",
36679
- showsVerticalScrollIndicator: false,
36680
- children: [
36681
- loading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles33.statusRow, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles33.statusText, children: "Loading..." }) }) : filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles33.statusRow, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles33.statusText, children: emptyText }) }) : null,
36682
- !loading && filteredOptions.map((option) => {
36683
- const isSelected = selectedValues.includes(option.value);
36684
- const isHovered = hoveredValue === option.value;
36685
- const visualState = isSelected ? "selected" : isHovered ? "hover" : "default";
36686
- return /* @__PURE__ */ jsxRuntime.jsxs(
36687
- reactNative.Pressable,
36688
- {
36689
- onPress: () => handleSelect(option.value),
36690
- disabled,
36691
- onHoverIn: () => setHoveredValue(option.value),
36692
- onHoverOut: () => setHoveredValue((current) => current === option.value ? null : current),
36693
- onPressIn: () => setHoveredValue(option.value),
36694
- onPressOut: () => setHoveredValue((current) => current === option.value ? null : current),
36695
- accessibilityRole: multiple ? "checkbox" : "button",
36696
- accessibilityState: { selected: isSelected, checked: isSelected, disabled },
36697
- style: [
36698
- styles33.item,
36699
- {
36700
- backgroundColor: itemBackground(visualState)
36701
- }
36702
- ],
36703
- children: [
36704
- multiple ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles33.itemCheckbox, { pointerEvents: "none" }], children: /* @__PURE__ */ jsxRuntime.jsx(Checkbox, { value: isSelected, variant: "light" }) }) : null,
36705
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles33.itemLabel, numberOfLines: 1, children: option.label })
36706
- ]
36707
- },
36708
- option.value
36709
- );
36710
- })
36711
- ]
36712
- }
36713
- )
36714
- ]
36715
- }
36716
- ) : null
36745
+ reactNative.Platform.OS === "web" ? webMenuPortal : menuNode
36717
36746
  ] }),
36718
36747
  showHint && hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles33.hint, { color: hintColor }], children: hint }) : null
36719
36748
  ]