@ahrowe/ui 0.50.0 → 0.51.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.
@@ -15,7 +15,7 @@ export interface ChatMessageListHandle {
15
15
  export interface ChatMessageListProps extends HtmlProps<HTMLDivElement> {
16
16
  /** `ChatMessage`s, and anything between them: a `Divider` with a date, a typing indicator. Oldest first. */
17
17
  children?: ReactNode;
18
- /** Loads older messages when the user scrolls to the top. Prepend them; the view stays where it was. Leave it out once there are no more. */
18
+ /** Loads older messages as the user nears the top. Prepend them; the view stays where it was. Leave it out once there are no more. */
19
19
  onLoadMore?: () => Promise<void>;
20
20
  ref?: Ref<ChatMessageListHandle>;
21
21
  /** Overrides the text ChatMessageList renders of its own. Merged per key. */
@@ -8,7 +8,7 @@ export interface DropdownItem {
8
8
  disabled?: boolean;
9
9
  }
10
10
  export type DropdownSlots = 'root' | 'label' | 'field' | 'control' | 'icon' | 'placeholder' | 'list' | 'option';
11
- export interface DropdownProps extends Omit<HtmlProps, 'onFocus' | 'onBlur' | 'onMouseEnter' | 'onMouseLeave'> {
11
+ export interface DropdownProps extends Omit<HtmlProps, 'onFocus' | 'onBlur' | 'onMouseEnter' | 'onMouseLeave' | 'onScroll'> {
12
12
  formValidator?: FormValidator | null;
13
13
  onMouseEnter?: ((event: React.MouseEvent) => void) | null;
14
14
  onMouseLeave?: ((event: React.MouseEvent) => void) | null;
@@ -27,7 +27,8 @@ export interface DropdownProps extends Omit<HtmlProps, 'onFocus' | 'onBlur' | 'o
27
27
  isRow?: boolean;
28
28
  disabled?: boolean;
29
29
  readOnly?: boolean;
30
- onScroll?: () => Promise<void>;
30
+ /** Loads more items as the end of the open list comes near. Pass `undefined` once there are no more. */
31
+ onLoadMore?: () => Promise<void>;
31
32
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
32
33
  dropdownLabel?: string;
33
34
  isDropdownListWider?: boolean;
@@ -1,5 +1,5 @@
1
1
  import { default as React } from 'react';
2
- export declare function useInfiniteScroll(containerRef: React.RefObject<HTMLDivElement | null>, onScroll: () => Promise<void>, disabled: boolean): {
2
+ export declare function useInfiniteScroll(containerRef: React.RefObject<HTMLDivElement | null>, onLoadMore: (() => Promise<void>) | undefined): {
3
3
  isLoading: boolean;
4
4
  sentinelRef: React.RefObject<HTMLDivElement | null>;
5
5
  };
@@ -83,12 +83,12 @@ button while it does:
83
83
  bottom by hand does the same.
84
84
  - **After sending**, call `scrollToBottom()` on the handle, or a user who sent from further up
85
85
  never sees their own message arrive.
86
- - **Older messages.** With `onLoadMore`, reaching the top calls it and shows a loader there.
86
+ - **Older messages.** With `onLoadMore`, nearing the top calls it and shows a loader there.
87
87
  Prepend what it loads; the message that was at the top stays exactly where it was, at the very
88
88
  top too, where the browser's own scroll anchoring does nothing and Safari has none at all. It is
89
- called again while the loaded page still leaves the top in view. If it rejects, it is called
90
- again after three seconds if the top is still in view, or whenever the user next scrolls there.
91
- Pass `undefined` once there is nothing older.
89
+ called again while the loaded page still leaves the top near, but not after one that added
90
+ nothing. If it rejects, it is called again after three seconds if the top is still near, or
91
+ whenever the user next scrolls there. Pass `undefined` once there is nothing older.
92
92
 
93
93
  **Sizing.** The list scrolls itself and fills the height it is given: `flex: 1` in a column, as
94
94
  above, or a `height`. A conversation shorter than the list sits at the bottom, by the composer. To
@@ -99,7 +99,7 @@ start at the top instead, as an AI chat often does: `styles={{ content: { margin
99
99
  | Prop | Type | Description |
100
100
  |------|------|-------------|
101
101
  | `children` | `ReactNode` | Messages, oldest first, and anything between them: a `Divider` with a date, a typing indicator |
102
- | `onLoadMore` | `() => Promise<void>` | Loads older messages when the top comes into view, see above |
102
+ | `onLoadMore` | `() => Promise<void>` | Loads older messages as the top comes near, see above |
103
103
  | `ref` | `Ref<ChatMessageListHandle>` | `scrollToBottom()`: back to the newest message, smoothly unless the user prefers reduced motion, and following again |
104
104
 
105
105
  **Accessibility:** the scrolling element is a `role="log"`, which screen readers announce
package/docs/Dropdown.md CHANGED
@@ -36,13 +36,16 @@ const statusValidator = new FormValidator('', [Validators.required()]);
36
36
  // Bottom sheet on small touch screens, anchored to the input everywhere else
37
37
  <Dropdown label="Status" items={items} value={status} onChange={onChange} presentation={Presentation.Auto} />
38
38
 
39
- // Infinite scroll support
39
+ // Infinite scroll: more items as the end of the open list comes near
40
40
  <Dropdown
41
41
  items={items}
42
- onScroll={async () => { await loadMoreItems(); }}
42
+ onLoadMore={hasMore ? loadMoreItems : undefined}
43
43
  />
44
44
  ```
45
45
 
46
+ Loading on scroll suits a list people browse. To find one entry among hundreds, typing beats
47
+ scrolling: InputDropdown filters as the user types.
48
+
46
49
  **Key props:**
47
50
 
48
51
  | Prop | Type | Description |
@@ -57,7 +60,7 @@ const statusValidator = new FormValidator('', [Validators.required()]);
57
60
  | `disabled` | `boolean` | Greys the field out and prevents interaction |
58
61
  | `readOnly` | `boolean` | Locks the selection without the disabled styling — the field keeps its normal appearance and stays focusable, but the menu won't open and the value can't change (the chevron is hidden). Default `false` |
59
62
  | `placeholder` | `string` | Placeholder when no selection |
60
- | `onScroll` | `() => Promise<void>` | Load more on list scroll |
63
+ | `onLoadMore` | `() => Promise<void>` | Loads more items as the end of the open list comes near, with a spinner meanwhile. Append what it loads. Called again while the end stays near, until a load appends nothing; pass `undefined` once there are no more to save that last call. A rejected load is tried again after three seconds |
61
64
  | `useMatLabelStyle` | `boolean` | Material-style floating label |
62
65
  | `presentation` | `Presentation` | Where the list opens: anchored to the input (`Default`), always a bottom sheet, or a sheet only on a small touch screen. Imported from the same package: `import { Presentation } from '@ahrowe/ui'` |
63
66
  | `alwaysFloatLabel` | `boolean` | Keep the material label floated in the border notch even when nothing is selected, so an empty dropdown reads as blank instead of showing the label as if it were the selected value. Only applies with `useMatLabelStyle` (default) |
@@ -87,3 +90,14 @@ outside (a wrapper, a grid column, `style={{ maxWidth: 240 }}`) rather than expe
87
90
  to the label: everything inside is absolutely positioned, so it has no width of its own to fall back on.
88
91
 
89
92
  **Slots:** `root` `label` `field` `control` `icon` `placeholder` `list` `option`
93
+
94
+ ## Migration from `onScroll`
95
+
96
+ Before 0.51 the load-more callback was `onScroll`, and it was never called. TypeScript points at
97
+ every use:
98
+
99
+ | Before | After |
100
+ |--------|-------|
101
+ | `onScroll={…}` | `onLoadMore={…}` |
102
+
103
+ Loading now actually happens, so pass `undefined` once there is nothing more to load.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahrowe/ui",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "description": "A React UI component library with theming, CSS Modules, and TypeScript support",
5
5
  "keywords": [
6
6
  "agent",