@truenas/ui-components 0.8.13 → 0.8.15

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.8.13",
3
+ "version": "0.8.15",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public"
@@ -18214,14 +18214,33 @@ export interface FileSystemItem {
18214
18214
  disabled?: boolean;
18215
18215
  }
18216
18216
  export interface FilePickerCallbacks {
18217
+ /**
18218
+ * Lists a directory. Reject with an Error to fail the navigation: the popup
18219
+ * shows its message above the listing (and `error` emits a `navigation`
18220
+ * error) until a later listing succeeds.
18221
+ */
18217
18222
  getChildren?: (path: string) => Promise<FileSystemItem[]>;
18218
18223
  validatePath?: (path: string) => Promise<boolean>;
18219
18224
  }
18225
+ /**
18226
+ * Everything that decides what the picker browses and how its value is
18227
+ * expressed, as one object — for consumers that build it from their own data
18228
+ * source and would otherwise bind the same four inputs at every call site.
18229
+ * Each field mirrors the input of the same name; an input that is bound
18230
+ * explicitly wins over the field.
18231
+ */
18232
+ export interface FilePickerSource {
18233
+ callbacks?: FilePickerCallbacks;
18234
+ rootPath?: string;
18235
+ startPath?: string;
18236
+ valueRoot?: string;
18237
+ }
18220
18238
  /**
18221
18239
  * A consumer-defined creation flow surfaced as a button in the picker footer,
18222
18240
  * e.g. creating a dataset or zvol through a dialog. The creation itself
18223
- * always happens outside the picker — either in a fully consumer-owned flow
18224
- * (`createAction` event) or through the pluggable inline flow (`create`).
18241
+ * always happens outside the picker — through the pluggable inline flow
18242
+ * (`create`), a consumer-owned flow the picker awaits (`run`), or, with
18243
+ * neither, a fully consumer-owned flow driven by the `createAction` event.
18225
18244
  */
18226
18245
  export interface FilePickerCreateAction {
18227
18246
  /** Identifies the action in `createAction` events, e.g. 'dataset'. */
@@ -18251,12 +18270,45 @@ export interface FilePickerCreateAction {
18251
18270
  * that (e.g. creation is cheap to leave behind, or idempotent).
18252
18271
  */
18253
18272
  create?: (parentPath: string, name: string) => Promise<string>;
18273
+ /**
18274
+ * A consumer-owned creation flow the picker awaits, e.g. one that opens a
18275
+ * dialog or side panel. Called with the browsed directory when the button is
18276
+ * pressed (and `create` is not set). Resolve with the created path to have
18277
+ * the picker refresh the listing and select it, exactly as after an inline
18278
+ * `create`; resolve with `null` when the user cancelled. Reject with an
18279
+ * Error to show its message in the popup (and emit a `creation` error).
18280
+ * Without `create` or `run`, pressing the button emits `createAction`.
18281
+ */
18282
+ run?: (parentPath: string) => Promise<string | null>;
18283
+ /**
18284
+ * Whether the action applies in the directory being browsed. Called with
18285
+ * that directory's path and its listed item — `null` when the picker has not
18286
+ * listed it, e.g. the start directory or the root. The button is hidden
18287
+ * where this returns false. Omit it to show the action everywhere.
18288
+ */
18289
+ canCreateAt?: (parentPath: string, directory: FileSystemItem | null) => boolean;
18254
18290
  }
18255
18291
  export interface FilePickerCreateActionEvent {
18256
18292
  actionId: string;
18257
18293
  /** Directory being browsed when the action was triggered. */
18258
18294
  parentPath: string;
18259
18295
  }
18296
+ /**
18297
+ * Payload of `itemSelectionChange`: the committed value together with the
18298
+ * listed items it refers to, for consumers that need more than the path
18299
+ * (e.g. to require that a FILE was picked).
18300
+ */
18301
+ export interface FilePickerSelection {
18302
+ /** The committed value — the same payload `selectionChange` carries. */
18303
+ value: string | string[];
18304
+ /**
18305
+ * One entry per selected path, in value order. `null` when the picker has
18306
+ * not listed that path in this session — e.g. a typed path the user never
18307
+ * browsed to, or the browsed directory itself submitted on an empty
18308
+ * selection before its parent was listed. Empty for a cleared selection.
18309
+ */
18310
+ items: (FileSystemItem | null)[];
18311
+ }
18260
18312
  export interface FilePickerError {
18261
18313
  type: 'navigation' | 'permission' | 'creation' | 'validation';
18262
18314
  message: string;
@@ -18281,6 +18333,24 @@ export declare class TnFilePickerComponent implements ControlValueAccessor, OnIn
18281
18333
  /** Consumer-defined creation flows shown as buttons in the popup footer. */
18282
18334
  createActions: import("@angular/core").InputSignal<FilePickerCreateAction[]>;
18283
18335
  allowManualInput: import("@angular/core").InputSignal<boolean>;
18336
+ /**
18337
+ * Commit typed input verbatim instead of treating it as a path to validate:
18338
+ * no `rootPath` confinement, no `callbacks.validatePath`, no `valueRoot`
18339
+ * mapping — the text the user typed is the value. Lets a field accept
18340
+ * entries the browser cannot list, e.g. `EXTERNAL:192.168.0.200\SHARE` or
18341
+ * a dataset name for a pool that is not imported. With `multiSelect`, the
18342
+ * text is split on commas into one entry per comma-separated part (trimmed,
18343
+ * empty parts dropped), so commas cannot appear inside an entry. Committing
18344
+ * closes the popup. Popup selections are unaffected. Requires
18345
+ * `allowManualInput`.
18346
+ */
18347
+ allowFreeText: import("@angular/core").InputSignal<boolean>;
18348
+ /**
18349
+ * Explicit accessible name for the path input. Inside a `tn-form-field` the
18350
+ * input is named by the field label automatically (via `aria-labelledby`),
18351
+ * so leave this unset there unless the field has no visible label.
18352
+ */
18353
+ ariaLabel: import("@angular/core").InputSignal<string | undefined>;
18284
18354
  /**
18285
18355
  * Open the picker popup when the input is clicked while no path is selected
18286
18356
  * yet — an empty field means the user is about to pick something, so the
@@ -18294,6 +18364,13 @@ export declare class TnFilePickerComponent implements ControlValueAccessor, OnIn
18294
18364
  openOnClick: import("@angular/core").InputSignal<boolean>;
18295
18365
  placeholder: import("@angular/core").InputSignal<string>;
18296
18366
  disabled: import("@angular/core").InputSignal<boolean>;
18367
+ /**
18368
+ * Shows the value without letting the user change it: the input cannot be
18369
+ * edited and the popup cannot be opened. Unlike `disabled` (or a disabled
18370
+ * form control) the field stays focusable and its value stays part of the
18371
+ * form — e.g. for a user who may view a setting but not edit it.
18372
+ */
18373
+ readonly: import("@angular/core").InputSignal<boolean>;
18297
18374
  /**
18298
18375
  * Test-id base applied to the file-picker container (`file-picker-` prefixed)
18299
18376
  * and derived onto the inner chrome: the path input (`input-<base>`), the
@@ -18303,7 +18380,13 @@ export declare class TnFilePickerComponent implements ControlValueAccessor, OnIn
18303
18380
  * (default `data-testid`).
18304
18381
  */
18305
18382
  testId: import("@angular/core").InputSignal<TnTestIdValue>;
18306
- startPath: import("@angular/core").InputSignal<string>;
18383
+ /**
18384
+ * Bundles `callbacks`, `rootPath`, `startPath` and `valueRoot` into one
18385
+ * object. Each of those inputs, when bound, wins over its field here.
18386
+ */
18387
+ source: import("@angular/core").InputSignal<FilePickerSource | undefined>;
18388
+ /** Directory the popup opens in. Defaults to `source.startPath`, else `/mnt`. */
18389
+ startPath: import("@angular/core").InputSignal<string | undefined>;
18307
18390
  /** Restricts navigation — users cannot navigate above this path. */
18308
18391
  rootPath: import("@angular/core").InputSignal<string | undefined>;
18309
18392
  /**
@@ -18319,8 +18402,16 @@ export declare class TnFilePickerComponent implements ControlValueAccessor, OnIn
18319
18402
  fileExtensions: import("@angular/core").InputSignal<string[] | undefined>;
18320
18403
  callbacks: import("@angular/core").InputSignal<FilePickerCallbacks | undefined>;
18321
18404
  selectionChange: import("@angular/core").OutputEmitterRef<string | string[]>;
18405
+ /**
18406
+ * Emitted alongside every `selectionChange` with the listed
18407
+ * `FileSystemItem` behind each selected path (or `null` when unknown).
18408
+ */
18409
+ itemSelectionChange: import("@angular/core").OutputEmitterRef<FilePickerSelection>;
18322
18410
  pathChange: import("@angular/core").OutputEmitterRef<string>;
18323
- /** Re-emitted from the popup when one of the `createActions` buttons is pressed. */
18411
+ /**
18412
+ * Emitted when one of the `createActions` buttons with neither `create` nor
18413
+ * `run` is pressed.
18414
+ */
18324
18415
  createAction: import("@angular/core").OutputEmitterRef<FilePickerCreateActionEvent>;
18325
18416
  error: import("@angular/core").OutputEmitterRef<FilePickerError>;
18326
18417
  wrapperEl: import("@angular/core").Signal<ElementRef<HTMLDivElement>>;
@@ -18335,8 +18426,32 @@ export declare class TnFilePickerComponent implements ControlValueAccessor, OnIn
18335
18426
  selectedItems: import("@angular/core").WritableSignal<string[]>;
18336
18427
  loading: import("@angular/core").WritableSignal<boolean>;
18337
18428
  hasError: import("@angular/core").WritableSignal<boolean>;
18429
+ /**
18430
+ * Message of the last failed listing or creation, shown in the popup until
18431
+ * a listing succeeds or the popup closes.
18432
+ */
18433
+ popupError: import("@angular/core").WritableSignal<string | null>;
18338
18434
  private formDisabled;
18435
+ /**
18436
+ * Field-derived ARIA for the path input when projected into a
18437
+ * `tn-form-field` (label, description, invalid, required). All-null when
18438
+ * standalone or when `ariaLabel` overrides.
18439
+ */
18440
+ protected readonly fieldAria: import("@truenas/ui-components").TnFormFieldAriaBindings;
18441
+ /**
18442
+ * Every item the picker has listed, by absolute path, so a selection can be
18443
+ * resolved back to its item even after browsing elsewhere (multi-select
18444
+ * across directories, `selectPath`, typed paths). Kept for the picker's
18445
+ * lifetime: a stale entry only means a stale `type` for a path that changed
18446
+ * on disk after it was listed.
18447
+ */
18448
+ private listedItems;
18339
18449
  isDisabled: import("@angular/core").Signal<boolean>;
18450
+ private effectiveCallbacks;
18451
+ private effectiveStartPath;
18452
+ private effectiveValueRoot;
18453
+ /** `createActions` that apply in the directory being browsed (`canCreateAt`). */
18454
+ protected availableCreateActions: import("@angular/core").Signal<FilePickerCreateAction[]>;
18340
18455
  /** Explicit `testId` wins; otherwise the bound control's name (formControlName etc.). */
18341
18456
  protected resolvedTestId: import("@angular/core").Signal<TnTestIdValue>;
18342
18457
  /**
@@ -18365,6 +18480,14 @@ export declare class TnFilePickerComponent implements ControlValueAccessor, OnIn
18365
18480
  * and spam `validatePath` with paths the user never meant.
18366
18481
  */
18367
18482
  onPathCommit(event: Event): void;
18483
+ /**
18484
+ * `allowFreeText` commit: the typed text is the value, verbatim (single
18485
+ * mode keeps even surrounding whitespace, as the field shows it), and a
18486
+ * field holding only whitespace clears the selection like an empty one.
18487
+ * Selection state still tracks the absolute form of each trimmed entry (the
18488
+ * same mapping `writeValue` applies) so reopening the popup reflects it.
18489
+ */
18490
+ private commitFreeText;
18368
18491
  /** `openOnClick` entry point. */
18369
18492
  onInputClick(): void;
18370
18493
  /**
@@ -18405,6 +18528,23 @@ export declare class TnFilePickerComponent implements ControlValueAccessor, OnIn
18405
18528
  navigateToPath(path: string): void;
18406
18529
  /** An inline `create` flow finished — show the new item selected and applied. */
18407
18530
  onCreated(path: string): void;
18531
+ /**
18532
+ * A footer button for an action without `create` was pressed: await its
18533
+ * `run` flow and select what it created, or hand the action to the consumer
18534
+ * through `createAction`.
18535
+ *
18536
+ * Deliberately not named `onCreateAction`: Storybook copies a story's props
18537
+ * onto the rendered component, and stories commonly name their
18538
+ * `(createAction)` handler that way, which would replace this method.
18539
+ */
18540
+ runCreateAction(event: FilePickerCreateActionEvent): Promise<void>;
18541
+ /**
18542
+ * `selectPath` for an item a create action just made. The user is looking at
18543
+ * the popup, so a path outside `rootPath` — which `selectPath` would only
18544
+ * report as a `validation` error, never shown there — is told as a
18545
+ * `creation` error in the popup instead of the press seeming to do nothing.
18546
+ */
18547
+ private selectCreatedPath;
18408
18548
  /**
18409
18549
  * Re-fetches the listing of the directory currently being browsed. Call it
18410
18550
  * when the directory changed outside the picker, e.g. after a
@@ -18446,6 +18586,14 @@ export declare class TnFilePickerComponent implements ControlValueAccessor, OnIn
18446
18586
  * would be the surprising behavior.
18447
18587
  */
18448
18588
  private updateSelection;
18589
+ /**
18590
+ * Emits `selectionChange` and `itemSelectionChange` for a committed value.
18591
+ * Items are looked up by the absolute form of each trimmed entry, which is
18592
+ * how listings are indexed — a free-text value keeps any surrounding
18593
+ * whitespace the user typed, but still resolves to the item it names.
18594
+ */
18595
+ private emitSelection;
18596
+ private indexListedItems;
18449
18597
  private normalizedValueRoot;
18450
18598
  /** Maps an internal absolute path into the `valueRoot`-relative value space. */
18451
18599
  private toValueSpace;
@@ -18475,6 +18623,16 @@ export declare class TnFilePickerComponent implements ControlValueAccessor, OnIn
18475
18623
  "required": false;
18476
18624
  "isSignal": true;
18477
18625
  };
18626
+ "allowFreeText": {
18627
+ "alias": "allowFreeText";
18628
+ "required": false;
18629
+ "isSignal": true;
18630
+ };
18631
+ "ariaLabel": {
18632
+ "alias": "ariaLabel";
18633
+ "required": false;
18634
+ "isSignal": true;
18635
+ };
18478
18636
  "openOnClick": {
18479
18637
  "alias": "openOnClick";
18480
18638
  "required": false;
@@ -18490,11 +18648,21 @@ export declare class TnFilePickerComponent implements ControlValueAccessor, OnIn
18490
18648
  "required": false;
18491
18649
  "isSignal": true;
18492
18650
  };
18651
+ "readonly": {
18652
+ "alias": "readonly";
18653
+ "required": false;
18654
+ "isSignal": true;
18655
+ };
18493
18656
  "testId": {
18494
18657
  "alias": "testId";
18495
18658
  "required": false;
18496
18659
  "isSignal": true;
18497
18660
  };
18661
+ "source": {
18662
+ "alias": "source";
18663
+ "required": false;
18664
+ "isSignal": true;
18665
+ };
18498
18666
  "startPath": {
18499
18667
  "alias": "startPath";
18500
18668
  "required": false;
@@ -18522,6 +18690,7 @@ export declare class TnFilePickerComponent implements ControlValueAccessor, OnIn
18522
18690
  };
18523
18691
  }, {
18524
18692
  "selectionChange": "selectionChange";
18693
+ "itemSelectionChange": "itemSelectionChange";
18525
18694
  "pathChange": "pathChange";
18526
18695
  "createAction": "createAction";
18527
18696
  "error": "error";
@@ -18605,31 +18774,35 @@ export declare class TnFilePickerHarness extends ComponentHarness {
18605
18774
  */
18606
18775
  getValue(): Promise<string>;
18607
18776
  /**
18608
- * Sets the picker's value by typing a single path into the path input and
18609
- * committing it via the input's `change` event, exactly as a user pressing
18610
- * Enter would.
18777
+ * Sets the picker's value by typing into the path input and committing it
18778
+ * via the input's `change` event, exactly as a user pressing Enter would.
18779
+ *
18780
+ * By default tn-file-picker treats typed input as ONE path even in
18781
+ * multi-select mode (commas are legal in paths, so they are never list
18782
+ * separators): committing a typed path REPLACES the whole selection with
18783
+ * that single path, and commits go through the component's normal
18784
+ * validation — paths outside `rootPath` are rejected (the value stays
18785
+ * unchanged and an `error` is emitted), and `callbacks.validatePath` is
18786
+ * consulted when provided. Build a real multi-selection through the popup
18787
+ * with {@link clickItem} + {@link clickSelect}.
18611
18788
  *
18612
- * Unlike `IxExplorerHarness.setValue` there is no `string[]` overload:
18613
- * tn-file-picker treats typed input as ONE path even in multi-select mode
18614
- * (commas are legal in paths, so they are never list separators). In
18615
- * multi-select, committing a typed path REPLACES the whole selection with
18616
- * that single path; build a real multi-selection through the popup with
18617
- * {@link clickItem} + {@link clickSelect}.
18789
+ * With `allowFreeText`, the typed text is committed verbatim with no
18790
+ * validation, and in multi-select it is split on commas — so passing a
18791
+ * `string[]` (typed joined with `', '`) sets one entry per element.
18618
18792
  *
18619
- * Commits go through the component's normal validation: paths outside
18620
- * `rootPath` are rejected (the value stays unchanged and an `error` is
18621
- * emitted), and `callbacks.validatePath` is consulted when provided.
18622
18793
  * Setting an empty string clears the selection. Has no effect when
18623
18794
  * `allowManualInput` is false (the input is readonly).
18624
18795
  *
18625
- * @param value The path to set.
18796
+ * @param value The path to set, or several entries for a free-text
18797
+ * multi-select picker.
18626
18798
  *
18627
18799
  * @example
18628
18800
  * ```typescript
18629
18801
  * await picker.setValue('/mnt/tank/media');
18802
+ * await freeTextMultiPicker.setValue(['/mnt/tank/a', 'EXTERNAL:host\\share']);
18630
18803
  * ```
18631
18804
  */
18632
- setValue(value: string): Promise<void>;
18805
+ setValue(value: string | string[]): Promise<void>;
18633
18806
  /**
18634
18807
  * Gets the path input's placeholder text.
18635
18808
  *
@@ -18824,8 +18997,8 @@ export declare class TnFilePickerPopupComponent implements AfterViewChecked {
18824
18997
  multiSelect: import("@angular/core").InputSignal<boolean>;
18825
18998
  /**
18826
18999
  * Consumer-defined creation flows rendered as buttons in the footer, next to the
18827
- * Select button. Pressing one emits `createAction` with the action id and the
18828
- * currently browsed path.
19000
+ * Select button. Pressing one without `create` emits `createAction` with the
19001
+ * action id and the currently browsed path; the owning picker runs its `run`.
18829
19002
  */
18830
19003
  createActions: import("@angular/core").InputSignal<FilePickerCreateAction[]>;
18831
19004
  currentPath: import("@angular/core").InputSignal<string>;
@@ -18838,6 +19011,8 @@ export declare class TnFilePickerPopupComponent implements AfterViewChecked {
18838
19011
  fileItems: import("@angular/core").InputSignal<FileSystemItem[]>;
18839
19012
  selectedItems: import("@angular/core").InputSignal<string[]>;
18840
19013
  loading: import("@angular/core").InputSignal<boolean>;
19014
+ /** Message of a failed listing or creation, shown above the listing. */
19015
+ errorMessage: import("@angular/core").InputSignal<string | null>;
18841
19016
  fileExtensions: import("@angular/core").InputSignal<string[] | undefined>;
18842
19017
  /**
18843
19018
  * Test-id base the owning picker resolved (explicit `testId`, else the bound
@@ -18900,6 +19075,7 @@ export declare class TnFilePickerPopupComponent implements AfterViewChecked {
18900
19075
  */
18901
19076
  protected readonly selectTestId: import("@angular/core").Signal<(string | number | null | undefined)[]>;
18902
19077
  protected readonly clearSelectionTestId: import("@angular/core").Signal<(string | number | null | undefined)[]>;
19078
+ protected readonly errorTestId: import("@angular/core").Signal<(string | number | null | undefined)[]>;
18903
19079
  /** Role-first segments for the inline creation input: `input-create[-<base>]`. */
18904
19080
  protected readonly inlineCreateTestId: import("@angular/core").Signal<(string | number | null | undefined)[]>;
18905
19081
  /**
@@ -19003,6 +19179,11 @@ export declare class TnFilePickerPopupComponent implements AfterViewChecked {
19003
19179
  "required": false;
19004
19180
  "isSignal": true;
19005
19181
  };
19182
+ "errorMessage": {
19183
+ "alias": "errorMessage";
19184
+ "required": false;
19185
+ "isSignal": true;
19186
+ };
19006
19187
  "fileExtensions": {
19007
19188
  "alias": "fileExtensions";
19008
19189
  "required": false;