@kahitsan/ksui 0.15.0 → 0.15.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kahitsan/ksui",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "ksui is a standalone set of SolidJS UI components for KahitSan/Hilinga and any SolidJS app. Published to the public npm registry and consumed as a normal dependency. Ships source under a `solid` export condition so the consumer's vite-plugin-solid compiles it with only solid-js externalized; it depends on nothing but solid-js + lucide-solid and injects its own CSS.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -20,11 +20,18 @@ const sizeMap: Record<string, number> = {
20
20
  * people; use AccountAvatar directly for financial accounts.
21
21
  */
22
22
  export default function Avatar(props: AvatarProps) {
23
+ // Getter-backed so name/image stay reactive: a parent rebinding them on a live
24
+ // Avatar (e.g. an in-session profile update) re-renders, rather than snapshotting
25
+ // the values once at component init.
23
26
  const account: AvatarAccount = {
24
27
  id: 0,
25
28
  type: "user",
26
- name: props.name,
27
- image: props.image,
29
+ get name() {
30
+ return props.name;
31
+ },
32
+ get image() {
33
+ return props.image;
34
+ },
28
35
  };
29
36
 
30
37
  return <AccountAvatar account={account} size={sizeMap[props.size ?? "md"]} class={props.class} />;
@@ -28,6 +28,10 @@ export interface SearchableSelectProps {
28
28
 
29
29
  const POPUP_MIN_WIDTH = 240;
30
30
  const POPUP_MAX_HEIGHT = 320; // keep in sync with max-h-80 below
31
+ // Flip the popup above the trigger only when the space below can't fit a
32
+ // usable list. Below this many px we'd rather open upward (if there's more room
33
+ // there) than cramp the results.
34
+ const POPUP_FLIP_THRESHOLD = 200;
31
35
 
32
36
  // Click-to-open combobox with inline search. The popup is rendered into a
33
37
  // Portal and positioned with `position: fixed`, so it can escape any ancestor
@@ -64,17 +68,22 @@ export default function SearchableSelect(props: SearchableSelectProps): JSX.Elem
64
68
  const width = Math.max(POPUP_MIN_WIDTH, rect.width);
65
69
  const spaceBelow = vpHeight - rect.bottom;
66
70
  const spaceAbove = rect.top;
67
- const flipUp = spaceBelow < POPUP_MAX_HEIGHT && spaceAbove > spaceBelow;
68
- const top = flipUp ? Math.max(8, rect.top - POPUP_MAX_HEIGHT - 4) : rect.bottom + 4;
71
+ // Only flip upward when there isn't enough usable room below AND there's
72
+ // genuinely more room above. Anchor the flipped popup by its BOTTOM (hugging
73
+ // the trigger) instead of computing a top from POPUP_MAX_HEIGHT, so a short
74
+ // result list stays adjacent to the trigger rather than floating far above it.
75
+ const flipUp = spaceBelow < POPUP_FLIP_THRESHOLD && spaceAbove > spaceBelow;
69
76
  const maxHeight = Math.max(
70
77
  160,
71
- Math.min(POPUP_MAX_HEIGHT, flipUp ? spaceAbove - 12 : spaceBelow - 12),
78
+ Math.min(POPUP_MAX_HEIGHT, (flipUp ? spaceAbove : spaceBelow) - 12),
72
79
  );
73
80
  // Clamp horizontally so the popup doesn't overflow the viewport edges.
74
81
  const left = Math.min(Math.max(8, rect.left), vpWidth - width - 8);
75
82
  setPopupStyle({
76
83
  position: "fixed",
77
- top: `${top}px`,
84
+ ...(flipUp
85
+ ? { bottom: `${Math.max(8, vpHeight - rect.top + 4)}px` }
86
+ : { top: `${rect.bottom + 4}px` }),
78
87
  left: `${left}px`,
79
88
  width: `${width}px`,
80
89
  "max-height": `${maxHeight}px`,