@truenas/ui-components 0.2.4 → 0.2.5

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": "@truenas/ui-components",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public"
@@ -176,6 +176,21 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
176
176
  disabled: _angular_core.InputSignal<boolean>;
177
177
  /** Require the user to select from the dropdown — reverts on blur if no match */
178
178
  requireSelection: _angular_core.InputSignal<boolean>;
179
+ /**
180
+ * Commit free text as the value: on blur or Enter, an unmatched search term
181
+ * becomes the control value instead of being discarded. For string-valued
182
+ * autocompletes (the typed text IS the value) — e.g. a device path picker
183
+ * where known devices are suggested but any path is acceptable. Mutually
184
+ * exclusive with `requireSelection`, which reverts unmatched text.
185
+ */
186
+ allowCustomValue: _angular_core.InputSignal<boolean>;
187
+ /**
188
+ * Show a loading row in the dropdown panel while options are being fetched.
189
+ * Pair with `searchChange`/`loadMore` for server-driven options.
190
+ */
191
+ loading: _angular_core.InputSignal<boolean>;
192
+ /** Text shown next to the spinner while `loading` is set. */
193
+ loadingText: _angular_core.InputSignal<string>;
179
194
  /** Custom filter function. Defaults to case-insensitive includes on displayWith text */
180
195
  filterFn: _angular_core.InputSignal<((option: T, searchTerm: string) => boolean) | undefined>;
181
196
  /** Text shown when no options match the search */
@@ -199,6 +214,29 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
199
214
  testId: _angular_core.InputSignal<TnTestIdValue>;
200
215
  /** Emits when an option is selected */
201
216
  optionSelected: _angular_core.OutputEmitterRef<T>;
217
+ /**
218
+ * Emits the search term as the user types (not on programmatic writes or
219
+ * option selection). Drive server-side filtering from this: fetch matches
220
+ * and update `options`. The component still applies its client-side filter
221
+ * on top — for pre-filtered server results, pass `[filterFn]` that returns
222
+ * `true`. Emissions are not debounced; debounce in the consumer if the
223
+ * lookup is expensive.
224
+ */
225
+ searchChange: _angular_core.OutputEmitterRef<string>;
226
+ /**
227
+ * Emits when the open dropdown is scrolled near its bottom — append the
228
+ * next page to `options` (and use `loading` while it fetches). Suppressed
229
+ * until the `options` COUNT changes so a slow consumer is not spammed —
230
+ * which also means replacing the array with a same-length page (e.g. a
231
+ * fixed-size window) does not re-arm the emitter; pagination must append.
232
+ */
233
+ loadMore: _angular_core.OutputEmitterRef<void>;
234
+ /**
235
+ * Emits every time the panel opens (focus, click, typing, ArrowDown). For
236
+ * click-to-suggest pickers, prime the first page from here when `options`
237
+ * is still empty — `searchChange` alone never fires until the user types.
238
+ */
239
+ opened: _angular_core.OutputEmitterRef<void>;
202
240
  /** Reference to the input element */
203
241
  inputEl: _angular_core.Signal<ElementRef<HTMLInputElement> | undefined>;
204
242
  /** Template for the dropdown panel, portaled into a CDK overlay on open. */
@@ -225,8 +263,17 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
225
263
  private onTouched;
226
264
  /** Live overlay holding the dropdown panel, or undefined when closed. */
227
265
  private overlayRef?;
266
+ /** Embedded view of the portaled panel — re-rendered before underfill measurement. */
267
+ private panelViewRef?;
228
268
  /** Subscriptions tied to the current overlay; torn down on close. */
229
269
  private overlaySubs;
270
+ /** Set after a loadMore emission; cleared when the options count changes. */
271
+ private loadMorePending;
272
+ /** Options count at the last effect run — length changes re-arm `loadMore`. */
273
+ private lastOptionsCount;
274
+ /** Scroll distance (px) from the panel bottom that triggers `loadMore`. */
275
+ private static readonly loadMoreThresholdPx;
276
+ constructor();
230
277
  ngOnDestroy(): void;
231
278
  writeValue(value: T | null): void;
232
279
  registerOnChange(fn: (value: T | null) => void): void;
@@ -237,6 +284,21 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
237
284
  onBlur(): void;
238
285
  onOptionClick(option: T): void;
239
286
  onKeydown(event: KeyboardEvent): void;
287
+ /**
288
+ * `loadMore` normally fires from a scroll event, which never happens when
289
+ * the current page is too short to overflow the panel — pagination would
290
+ * dead-end with data still available. When new rows land (or the panel
291
+ * opens), emit once more if the panel has no scrollbar.
292
+ */
293
+ private checkUnderfill;
294
+ onPanelScroll(event: Event): void;
295
+ /**
296
+ * Commit the current search term as the value (allowCustomValue mode). A
297
+ * display match (case-insensitive, same as the requireSelection path)
298
+ * commits the matching option instead, so picking an existing entry by
299
+ * typing its full text behaves like clicking it.
300
+ */
301
+ private commitCustomValue;
240
302
  private selectOption;
241
303
  private open;
242
304
  private close;
@@ -253,7 +315,7 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
253
315
  private detachOverlay;
254
316
  private scrollToHighlighted;
255
317
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnAutocompleteComponent<any>, never>;
256
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnAutocompleteComponent<any>, "tn-autocomplete", never, { "options": { "alias": "options"; "required": false; "isSignal": true; }; "displayWith": { "alias": "displayWith"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "requireSelection": { "alias": "requireSelection"; "required": false; "isSignal": true; }; "filterFn": { "alias": "filterFn"; "required": false; "isSignal": true; }; "noResultsText": { "alias": "noResultsText"; "required": false; "isSignal": true; }; "maxResults": { "alias": "maxResults"; "required": false; "isSignal": true; }; "panelMaxHeight": { "alias": "panelMaxHeight"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, { "optionSelected": "optionSelected"; }, never, never, true, never>;
318
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnAutocompleteComponent<any>, "tn-autocomplete", never, { "options": { "alias": "options"; "required": false; "isSignal": true; }; "displayWith": { "alias": "displayWith"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "requireSelection": { "alias": "requireSelection"; "required": false; "isSignal": true; }; "allowCustomValue": { "alias": "allowCustomValue"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "loadingText": { "alias": "loadingText"; "required": false; "isSignal": true; }; "filterFn": { "alias": "filterFn"; "required": false; "isSignal": true; }; "noResultsText": { "alias": "noResultsText"; "required": false; "isSignal": true; }; "maxResults": { "alias": "maxResults"; "required": false; "isSignal": true; }; "panelMaxHeight": { "alias": "panelMaxHeight"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, { "optionSelected": "optionSelected"; "searchChange": "searchChange"; "loadMore": "loadMore"; "opened": "opened"; }, never, never, true, never>;
257
319
  }
258
320
 
259
321
  /**
@@ -426,6 +488,19 @@ declare class TnAutocompleteHarness extends ComponentHarness {
426
488
  * ```
427
489
  */
428
490
  blur(): Promise<void>;
491
+ /**
492
+ * Checks whether the dropdown panel is showing the loading row.
493
+ *
494
+ * @returns Promise resolving to true if the loading indicator is visible.
495
+ *
496
+ * @example
497
+ * ```typescript
498
+ * const ac = await loader.getHarness(TnAutocompleteHarness);
499
+ * await ac.focus();
500
+ * expect(await ac.isLoading()).toBe(true);
501
+ * ```
502
+ */
503
+ isLoading(): Promise<boolean>;
429
504
  }
430
505
  /**
431
506
  * A set of criteria that can be used to filter a list of `TnAutocompleteHarness` instances.