@filip.mazev/blocks 1.4.15 → 1.4.17

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.
@@ -0,0 +1,80 @@
1
+ # Forms
2
+
3
+ Blocks fields use Angular 22 native signal control contracts and support Signal Forms, Reactive Forms, Template-driven Forms and standalone models. The library supplies presentation, interaction and accessible feedback; Angular owns validation and submission.
4
+
5
+ ## Import and basic usage
6
+
7
+ ```ts
8
+ import { signal } from '@angular/core';
9
+ import { form, FormField as SignalFormField, FormRoot, required, email } from '@angular/forms/signals';
10
+ import { Button, InputField } from '@filip.mazev/blocks/forms';
11
+
12
+ readonly accountModel = signal({ email: '' });
13
+ readonly account = form(this.accountModel, (p) => {
14
+ required(p.email, { message: 'Email is required.' });
15
+ email(p.email, { message: 'Enter a valid email address.' });
16
+ }, {
17
+ submission: {
18
+ ignoreValidators: 'none',
19
+ action: async (field) => { await this.api.save(field().value()); }
20
+ }
21
+ });
22
+ ```
23
+
24
+ Import `SignalFormField`, `FormRoot`, and the used Blocks controls into the standalone component. Alias Angular's directive because Blocks also exports its frame component as `FormField`.
25
+
26
+ ```html
27
+ <form [formRoot]="account">
28
+ <bx-input-field label="Email" type="email" [formField]="account.email" />
29
+ <button bx-button type="submit" [disabled]="!account().valid()" [loading]="account().submitting()">Save</button>
30
+ </form>
31
+ ```
32
+
33
+ ## Validation and state
34
+
35
+ Schema validation supplies `required`, numeric and length limits, pattern metadata, errors, pending, disabled and readonly state to compatible controls. `valid()` is false while asynchronous validation is pending; `!invalid()` alone does not guarantee readiness.
36
+
37
+ Use `ignoreValidators: 'none'` to gate the submission handler as well as the visible button while async validation is pending. Submission marks interactive fields touched, prevents duplicate submissions, and applies returned server errors to their target fields.
38
+
39
+ Read field state through the Angular field tree. Derive UI with `computed()` and use `effect()` for side effects when validity or pending state changes. Cross-field validators belong in the schema and can read related values. Blocks does not implement a second validation engine.
40
+
41
+ ## Custom messages and visibility
42
+
43
+ Shared inputs include `errorMessages`, `errorPriority`, `errorMode` (`first` or `all`), and `errorPolicy` (`touched`, `dirty`, `submitted`, `always`, or a function receiving `IErrorState`). The default reveals invalid fields after touch or submission. A function can include custom group or application state. The submitted policy tracks native form submission; when invoking submission programmatically outside the form element, pass the shared `submitted` input from application state.
44
+
45
+ Message resolution prefers projected `bx-error` content, then field message overrides, schema messages, and configured defaults. `minlength`/`maxlength` Reactive Forms keys and `minLength`/`maxLength` Signal Forms kinds are recognized. Resolvers receive the original error plus its parameter payload.
46
+
47
+ ```ts
48
+ readonly messages: ValidationMessages = {
49
+ minLength: ({ params }) => `Use at least ${params['minLength'] ?? params['requiredLength']} characters.`,
50
+ required: { key: 'validation.required' }
51
+ };
52
+ ```
53
+
54
+ Put this declaration in the appropriate constants or types-supporting module according to the house rules. Translation-key overrides receive error parameters through the existing adapter. Default messages can be replaced application-wide using `FormSettingsService.update()`; instance inputs take precedence. No error-message map is mandatory when the schema supplies messages.
55
+
56
+ Projected `bx-error` supports rich content. `bx-hint`, `hint`, `hintEnd`, `bxPrefix`, and `bxSuffix` provide composable support and adornments. Supporting text and errors are associated automatically through `aria-describedby`. Custom error regions announce changes politely.
57
+
58
+ ## Compatibility and migration
59
+
60
+ Existing `[formControl]`, `formControlName`, and `[(ngModel)]` component bindings remain supported. Text edits honor Reactive Forms blur and submit update strategies. `reset()` on the owning field/form clears component interaction and parse state. Standalone value controls expose `[(value)]`; checkbox-style controls expose `[(checked)]`.
61
+
62
+ `BxBaseControl` remains available for existing external CVA subclasses; repeated touch reporting and schema error-message handling are corrected. New Blocks controls use native signal contracts, without implementing CVA on the same component. The legacy individually bound radio remains available, while new forms should bind a `RadioGroup` once around its options.
63
+
64
+ Native `input`, `textarea` and `select` elements can use `bxInput` inside `bx-form-field`. Consumer components can provide `BX_FORM_FIELD_CONTROL` with `IFormFieldControl` to integrate their own state and focus behavior.
65
+
66
+ ## Asynchronous options
67
+
68
+ Single select and multiselect support application-owned requests, comparison, templates, incremental loading and error/retry behavior. Their scroll wrappers use `LoadMoreDirective`, which can also be used by future paginated components. See the [load-more guide](directives/load-more/README.md) and [multiselect guide](components/multi-select/README.md).
69
+
70
+ ## Appearance and accessibility
71
+
72
+ Controls follow Blocks semantic tokens, global theme/appearance configuration, reduced motion through `BxA11yService`, and reactive text resolution through `BX_I18N`. Configuration supports filled/outlined appearance, label placement, required markers, floating-label behavior and supporting-text sizing. Readonly and disabled states remain distinct in form state. All applicable controls accept `readOnly` as well as Angular Signal Forms' lowercase `readonly` contract. Either true value prevents edits while retaining focusability. Readonly controls use the default cursor and suppress pointer hover/click highlights and interactive glass lighting while preserving their static appearance and keyboard focus styling. Focus origin uses the shared Angular CDK focus monitor. Changes to readonly state update mounted surfaces, including projected native controls and grouped radios. Native select integration prevents interaction without binding a nonexistent native readOnly property.
73
+
74
+ All existing form ellipsis and full-selection fit measurement use the public `TruncateDirective` from `@filip.mazev/blocks/primitives`. Set `[tooltipOnTruncate]="false"` on an input, search, select, or multiselect to retain truncation while supplying your own tooltip. `FormSettingsService.update({ tooltipOnTruncate: false })` changes the application default; instance inputs take precedence. Tag and menu controls expose the same opt-out. The public `TruncatedTextPipe` (`bxTruncatedText`) and `truncatedText()` helper support custom feedback. See the [shared truncation guide](../primitives/directives/truncate/README.md).
75
+
76
+ ## Playground
77
+
78
+ Open `/components/signal-forms` for multiple controls, sync/async/cross-field rules, pending state, custom messages, server errors, disabled submission, dynamic disabled/readonly, reset and reactive validation status. Use Fill valid data to exercise submission, or an email containing `server-error` to see a server rejection.
79
+
80
+ The load-more and selection pages include fake APIs, failed requests, retry and small pages without scrollbars.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@filip.mazev/blocks",
3
- "version": "1.4.15",
3
+ "version": "1.4.17",
4
4
  "license": "MIT",
5
5
  "schematics": "./schematics/collection.json",
6
6
  "exports": {
@@ -216,10 +216,13 @@ export declare class BxShortcutService {
216
216
  static ɵprov: i0.ɵɵInjectableDeclaration<any>;
217
217
  }
218
218
  export declare abstract class BxBaseControl<T> implements ControlValueAccessor, OnInit {
219
- protected readonly isFieldRequired: import("@angular/core").Signal<boolean>;
219
+ private readonly focusMonitor;
220
+ private readonly focusElement;
221
+ private readonly destroyRef;
220
222
  protected readonly internalValue: import("@angular/core").WritableSignal<T | null>;
221
223
  protected readonly isTouched: import("@angular/core").WritableSignal<boolean>;
222
224
  protected readonly isDisabled: import("@angular/core").WritableSignal<boolean>;
225
+ protected readonly keyboardFocused: import("@angular/core").WritableSignal<boolean>;
223
226
  readonly ngControl: NgControl | null;
224
227
  readonly label: import("@angular/core").InputSignal<ResolvableText | undefined>;
225
228
  readonly placeholder: import("@angular/core").InputSignal<ResolvableText | undefined>;
@@ -228,6 +231,9 @@ export declare abstract class BxBaseControl<T> implements ControlValueAccessor,
228
231
  readonly ariaLabel: import("@angular/core").InputSignal<string | undefined>;
229
232
  readonly errorMessages: import("@angular/core").InputSignal<Partial<Record<ValidatorKey, ResolvableText>>>;
230
233
  readonly required: import("@angular/core").InputSignal<boolean>;
234
+ readonly readonly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
235
+ readonly readOnly: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
236
+ readonly isReadonly: import("@angular/core").Signal<boolean>;
231
237
  readonly loading: import("@angular/core").InputSignal<boolean>;
232
238
  readonly disabled: import("@angular/core").InputSignal<boolean>;
233
239
  protected onChange: (value: T | null) => void;
@@ -240,6 +246,8 @@ export declare abstract class BxBaseControl<T> implements ControlValueAccessor,
240
246
  setDisabledState(isDisabled: boolean): void;
241
247
  protected updateValue(value: T | null): void;
242
248
  protected markAsTouched(): void;
249
+ protected onControlPointerDown(): void;
250
+ protected isFieldRequired(): boolean;
243
251
  protected get errorMessage(): ResolvableText | undefined;
244
252
  static ɵfac: i0.ɵɵFactoryDeclaration<BxBaseControl<any>, never>;
245
253
  static ɵdir: i0.ɵɵDirectiveDeclaration<BxBaseControl<any>, never, never, {
@@ -278,6 +286,16 @@ export declare abstract class BxBaseControl<T> implements ControlValueAccessor,
278
286
  "required": false;
279
287
  "isSignal": true;
280
288
  };
289
+ "readonly": {
290
+ "alias": "readonly";
291
+ "required": false;
292
+ "isSignal": true;
293
+ };
294
+ "readOnly": {
295
+ "alias": "readOnly";
296
+ "required": false;
297
+ "isSignal": true;
298
+ };
281
299
  "loading": {
282
300
  "alias": "loading";
283
301
  "required": false;
@@ -353,4 +371,6 @@ export declare function invertPaletteToken(token: Color): Color;
353
371
  export declare function resolveThemeId(value: string | null | undefined): BxThemeId | undefined;
354
372
  export declare function uuidv4(): string;
355
373
  export declare function inheritOverlayTheme(source: HTMLElement, pane: HTMLElement): () => void;
356
- export declare function trackOverlayOrigin(overlay: OverlayRef, origin: HTMLElement): () => void;
374
+ export declare function trackOverlayOrigin(overlay: OverlayRef, origin: HTMLElement): () => void;
375
+ /** Resolves text and tracks the adapter's language signal in the calling computation. */
376
+ export declare function resolveText(text: ResolvableText | undefined, adapter: IBxI18nAdapter | null, params?: Record<string, unknown>): string;
@@ -47,7 +47,7 @@ export declare class BxSurfaceDirective implements AfterViewInit {
47
47
  }, {}, never, never, true, never>;
48
48
  }
49
49
  /** Internal component integration. An explicit bxSurface directive takes precedence. */
50
- export declare function injectComponentSurface(options?: () => SurfaceOptions$1): void;
50
+ export declare function injectComponentSurface(options?: () => SurfaceOptions$1, enabled?: () => boolean): void;
51
51
  export interface BxGlassLight {
52
52
  update(light: GlassLight): void;
53
53
  destroy(): void;