@frame-kit/ui-ng 0.2.8 → 0.2.10

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/COMPONENTS.md CHANGED
@@ -83,6 +83,7 @@ Organized by **purpose** (not Atomic Design) into six layers with strict depende
83
83
  ### Services — runtime services with no UI
84
84
 
85
85
  - [OverlayOrchestrator](#overlayorchestrator)
86
+ - [ReactiveI18nService](#reactivei18nservice)
86
87
  - [SpotlightService](#spotlightservice)
87
88
  - [ToastService](#toastservice)
88
89
 
@@ -631,6 +632,13 @@ Centralized priority stack for dialogs, drawers, popovers, and navigation panels
631
632
 
632
633
  ---
633
634
 
635
+ ### ReactiveI18nService
636
+
637
+ Message lookup by dot-path key with `{{var}}` interpolation and a minimal ICU plural subset, backed by signals so a locale switch re-renders every template reading it. Provided with `provideI18n`, injected via `I18N_SERVICE`; the framework-free core lives in `@frame-kit/utils` so a server can render the same copy.
638
+ [README](https://github.com/frame-kit/packages/blob/main/packages/ui-ng/services/i18n/README.md)
639
+
640
+ ---
641
+
634
642
  ### SpotlightService
635
643
 
636
644
  Coordinates a single global "active key" with reference-counted holders, so unrelated parts of the DOM can pulse together when the same key is activated. The `fkSpotlightTrigger` / `fkSpotlightTarget` directives are the typical consumers.
@@ -289,6 +289,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
289
289
  args: ['attr.for']
290
290
  }] } });
291
291
 
292
+ const FK_LABEL_ACTION = new InjectionToken('FK_LABEL_ACTION');
293
+ /**
294
+ * Marks content that belongs on the label's own row, aligned to the trailing
295
+ * edge — a "Forgot password?" link beside a Password label being the canonical
296
+ * case.
297
+ *
298
+ * It sits outside the `<label>` element deliberately. A link inside a label is
299
+ * activated when the label is clicked, so the user reaches for the link and
300
+ * focuses the input instead; and its text would join the input's accessible
301
+ * name.
302
+ */
303
+ class FkLabelActionDirective {
304
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FkLabelActionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
305
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: FkLabelActionDirective, isStandalone: true, selector: "[fkLabelAction]", providers: [{ provide: FK_LABEL_ACTION, useExisting: FkLabelActionDirective }], ngImport: i0 });
306
+ }
307
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FkLabelActionDirective, decorators: [{
308
+ type: Directive,
309
+ args: [{
310
+ selector: '[fkLabelAction]',
311
+ standalone: true,
312
+ providers: [{ provide: FK_LABEL_ACTION, useExisting: FkLabelActionDirective }],
313
+ }]
314
+ }] });
315
+
292
316
  const FK_PREFIX = new InjectionToken('FK_PREFIX');
293
317
  class FkPrefixDirective {
294
318
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FkPrefixDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -349,6 +373,7 @@ class FormFieldComponent {
349
373
  hintRef = contentChild(HintComponent, ...(ngDevMode ? [{ debugName: "hintRef" }] : /* istanbul ignore next */ []));
350
374
  errorRef = contentChild(ErrorComponent, ...(ngDevMode ? [{ debugName: "errorRef" }] : /* istanbul ignore next */ []));
351
375
  labelRef = contentChild(LabelComponent, ...(ngDevMode ? [{ debugName: "labelRef" }] : /* istanbul ignore next */ []));
376
+ labelActionRef = contentChild(FK_LABEL_ACTION, ...(ngDevMode ? [{ debugName: "labelActionRef" }] : /* istanbul ignore next */ []));
352
377
  prefixRef = contentChild(FK_PREFIX, ...(ngDevMode ? [{ debugName: "prefixRef" }] : /* istanbul ignore next */ []));
353
378
  suffixRef = contentChild(FK_SUFFIX, ...(ngDevMode ? [{ debugName: "suffixRef" }] : /* istanbul ignore next */ []));
354
379
  // ===== INJECTED SERVICES =====
@@ -375,6 +400,12 @@ class FormFieldComponent {
375
400
  warnOnUnresolvedProjection() {
376
401
  const checks = [
377
402
  ['fkPrefix', 'FkPrefixDirective', 'prefix', this.prefixRef()],
403
+ [
404
+ 'fkLabelAction',
405
+ 'FkLabelActionDirective',
406
+ 'label-action',
407
+ this.labelActionRef(),
408
+ ],
378
409
  ['fkSuffix', 'FkSuffixDirective', 'suffix', this.suffixRef()],
379
410
  ];
380
411
  for (const [attribute, directive, slot, resolved] of checks) {
@@ -535,12 +566,12 @@ class FormFieldComponent {
535
566
  control.setDescribedByIds(ids);
536
567
  }
537
568
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
538
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: FormFieldComponent, isStandalone: true, selector: "fk-form-field", inputs: { className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, showErrorsOn: { classPropertyName: "showErrorsOn", publicName: "showErrorsOn", isSignal: true, isRequired: false, transformFunction: null }, submitted: { classPropertyName: "submitted", publicName: "submitted", isSignal: true, isRequired: false, transformFunction: null }, externalDescribedBy: { classPropertyName: "externalDescribedBy", publicName: "externalDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, fallbackControl: { classPropertyName: "fallbackControl", publicName: "fallbackControl", isSignal: true, isRequired: false, transformFunction: null }, errorMessages: { classPropertyName: "errorMessages", publicName: "errorMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass" } }, queries: [{ propertyName: "control", first: true, predicate: FIELD_CONTROL, descendants: true, isSignal: true }, { propertyName: "hintRef", first: true, predicate: HintComponent, descendants: true, isSignal: true }, { propertyName: "errorRef", first: true, predicate: ErrorComponent, descendants: true, isSignal: true }, { propertyName: "labelRef", first: true, predicate: LabelComponent, descendants: true, isSignal: true }, { propertyName: "prefixRef", first: true, predicate: FK_PREFIX, descendants: true, isSignal: true }, { propertyName: "suffixRef", first: true, predicate: FK_SUFFIX, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fk-form-field__wrapper\">\n @if (label() || labelRef()) {\n <div class=\"fk-form-field__label-row\">\n <label\n class=\"fk-form-field__label\"\n [attr.id]=\"labelId\"\n [attr.for]=\"control()?.id ?? null\"\n >\n @if (label()) {\n {{ label() }}\n } @else {\n <ng-content select=\"fk-label\" />\n }\n </label>\n <!-- The required marker lives OUTSIDE the <label> so it never leaks into\n the label's text/accessible name (keeping getByLabel + AT clean); the\n input's aria-required conveys the requirement to assistive tech. -->\n @if (isRequired()) {\n <span class=\"fk-form-field__required\" aria-hidden=\"true\">*</span>\n }\n </div>\n }\n\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"fk-form-field__control\"\n (click)=\"control()?.onContainerClick?.($event)\"\n >\n <!-- Rendered unconditionally, and deliberately so. Gating these on the\n directive resolving means a consumer who marks an element `fkPrefix`\n but forgets to import `FkPrefixDirective` gets no slot, and because an\n unprojected node is never placed in the DOM at all, their icon\n disappears with no error anywhere. Both wrappers are absolutely\n positioned and `:empty`-hidden, so rendering them always costs nothing\n and turns that failure into something visible. -->\n <div class=\"fk-form-field__prefix\">\n <ng-content select=\"[fkPrefix]\" />\n </div>\n <ng-content />\n <div class=\"fk-form-field__suffix\">\n <ng-content select=\"[fkSuffix]\" />\n </div>\n </div>\n\n <div class=\"fk-form-field__subscript\">\n @if (showError()) {\n @if (hasAutoError) {\n <span class=\"fk-error\" role=\"alert\" [attr.id]=\"autoErrorId\">\n {{ autoErrorMessage() }}\n </span>\n } @else {\n <ng-content select=\"fk-error\" />\n }\n } @else {\n <ng-content select=\"fk-hint\" />\n }\n </div>\n</div>\n", styles: [":host{display:block}.fk-form-field__wrapper{display:flex;flex-direction:column}.fk-form-field__label-row{display:flex;align-items:center;gap:.25em;margin-top:var(--fk-form-label-margin-top, var(--fk-rhythm-2, .5rem))}.fk-form-field__label{display:flex;align-items:center;gap:.25em;font-family:var(--fk-font-family-base, \"Inter\", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\");font-size:var(--fk-form-label-font-size, var(--fk-typography-label-font-size, .8125rem));font-weight:var(--fk-form-label-font-weight, var(--fk-typography-label-font-weight, 600));line-height:var(--fk-form-label-line-height, var(--fk-typography-label-line-height, 1.25));color:var(--fk-form-label-color, var(--fk-color-text-strong, #0b1420));cursor:default}.fk-form-field__label fk-icon{flex:none;color:var(--fk-form-label-icon-color, currentColor)}.fk-form-field__required{color:var(--fk-form-error-color, var(--fk-color-danger, #e02424))}.fk-form-field__control{position:relative;margin-top:var(--fk-form-label-gap, var(--fk-rhythm-2, .5rem))}.fk-form-field__prefix,.fk-form-field__suffix{position:absolute;top:50%;transform:translateY(-50%);display:flex;align-items:center;color:var(--fk-input-placeholder-color, var(--fk-color-muted, #8a98a8));z-index:1}.fk-form-field__prefix{pointer-events:none}.fk-form-field__prefix:empty,.fk-form-field__suffix:empty{display:none}.fk-form-field__prefix{left:var(--fk-input-affix-offset, .75rem)}.fk-form-field__suffix{right:var(--fk-input-affix-offset, .75rem)}.fk-form-field__subscript{display:var(--fk-form-subscript-display, block);min-height:var(--fk-form-subscript-min-height, calc(var(--fk-form-hint-font-size, var(--fk-typography-small-font-size, .8125rem)) * 1.4));margin-top:var(--fk-form-subscript-gap, var(--fk-rhythm-1, .25rem))}.fk-form-field__subscript .fk-error{display:block;font-family:var(--fk-font-family-base, \"Inter\", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\");font-size:var(--fk-form-error-font-size, var(--fk-typography-small-font-size, .8125rem));line-height:1.4;color:var(--fk-form-error-color, var(--fk-color-danger, #e02424))}:host(.fk-form-field--focused) .fk-form-field__label{color:var(--fk-input-border-color-focus, var(--fk-color-primary, #0a84ff))}:host(.fk-form-field--disabled) .fk-form-field__label{opacity:var(--fk-input-disabled-opacity, .5);cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
569
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: FormFieldComponent, isStandalone: true, selector: "fk-form-field", inputs: { className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, showErrorsOn: { classPropertyName: "showErrorsOn", publicName: "showErrorsOn", isSignal: true, isRequired: false, transformFunction: null }, submitted: { classPropertyName: "submitted", publicName: "submitted", isSignal: true, isRequired: false, transformFunction: null }, externalDescribedBy: { classPropertyName: "externalDescribedBy", publicName: "externalDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, fallbackControl: { classPropertyName: "fallbackControl", publicName: "fallbackControl", isSignal: true, isRequired: false, transformFunction: null }, errorMessages: { classPropertyName: "errorMessages", publicName: "errorMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass" } }, queries: [{ propertyName: "control", first: true, predicate: FIELD_CONTROL, descendants: true, isSignal: true }, { propertyName: "hintRef", first: true, predicate: HintComponent, descendants: true, isSignal: true }, { propertyName: "errorRef", first: true, predicate: ErrorComponent, descendants: true, isSignal: true }, { propertyName: "labelRef", first: true, predicate: LabelComponent, descendants: true, isSignal: true }, { propertyName: "labelActionRef", first: true, predicate: FK_LABEL_ACTION, descendants: true, isSignal: true }, { propertyName: "prefixRef", first: true, predicate: FK_PREFIX, descendants: true, isSignal: true }, { propertyName: "suffixRef", first: true, predicate: FK_SUFFIX, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fk-form-field__wrapper\">\n @if (label() || labelRef()) {\n <div class=\"fk-form-field__label-row\">\n <label\n class=\"fk-form-field__label\"\n [attr.id]=\"labelId\"\n [attr.for]=\"control()?.id ?? null\"\n >\n @if (label()) {\n {{ label() }}\n } @else {\n <ng-content select=\"fk-label\" />\n }\n </label>\n <!-- The required marker lives OUTSIDE the <label> so it never leaks into\n the label's text/accessible name (keeping getByLabel + AT clean); the\n input's aria-required conveys the requirement to assistive tech. -->\n @if (isRequired()) {\n <span class=\"fk-form-field__required\" aria-hidden=\"true\">*</span>\n }\n\n <!-- Trailing content on the label's row. Outside the <label> so it is not\n activated by a label click and does not join the input's accessible\n name. Rendered unconditionally for the same reason as the prefix and\n suffix slots: an unprojected node is never placed in the DOM, so\n gating this on the directive would make a forgotten import vanish\n silently. `:empty` keeps it from taking space. -->\n <div class=\"fk-form-field__label-action\">\n <ng-content select=\"[fkLabelAction]\" />\n </div>\n </div>\n }\n\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"fk-form-field__control\"\n (click)=\"control()?.onContainerClick?.($event)\"\n >\n <!-- Rendered unconditionally, and deliberately so. Gating these on the\n directive resolving means a consumer who marks an element `fkPrefix`\n but forgets to import `FkPrefixDirective` gets no slot, and because an\n unprojected node is never placed in the DOM at all, their icon\n disappears with no error anywhere. Both wrappers are absolutely\n positioned and `:empty`-hidden, so rendering them always costs nothing\n and turns that failure into something visible. -->\n <div class=\"fk-form-field__prefix\">\n <ng-content select=\"[fkPrefix]\" />\n </div>\n <ng-content />\n <div class=\"fk-form-field__suffix\">\n <ng-content select=\"[fkSuffix]\" />\n </div>\n </div>\n\n <div class=\"fk-form-field__subscript\">\n @if (showError()) {\n @if (hasAutoError) {\n <span class=\"fk-error\" role=\"alert\" [attr.id]=\"autoErrorId\">\n {{ autoErrorMessage() }}\n </span>\n } @else {\n <ng-content select=\"fk-error\" />\n }\n } @else {\n <ng-content select=\"fk-hint\" />\n }\n </div>\n</div>\n", styles: [":host{display:block}.fk-form-field__wrapper{display:flex;flex-direction:column}.fk-form-field__label-action{margin-inline-start:auto;display:flex;align-items:baseline}.fk-form-field__label-action:empty{display:none}.fk-form-field__label-row{display:flex;align-items:center;gap:.25em;margin-top:var(--fk-form-label-margin-top, var(--fk-rhythm-2, .5rem))}.fk-form-field__label{display:flex;align-items:center;gap:.25em;font-family:var(--fk-font-family-base, \"Inter\", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\");font-size:var(--fk-form-label-font-size, var(--fk-typography-label-font-size, .8125rem));font-weight:var(--fk-form-label-font-weight, var(--fk-typography-label-font-weight, 600));line-height:var(--fk-form-label-line-height, var(--fk-typography-label-line-height, 1.25));color:var(--fk-form-label-color, var(--fk-color-text-strong, #0b1420));cursor:default}.fk-form-field__label fk-icon{flex:none;color:var(--fk-form-label-icon-color, currentColor)}.fk-form-field__required{color:var(--fk-form-error-color, var(--fk-color-danger, #e02424))}.fk-form-field__control{position:relative;margin-top:var(--fk-form-label-gap, var(--fk-rhythm-2, .5rem))}.fk-form-field__prefix,.fk-form-field__suffix{position:absolute;top:50%;transform:translateY(-50%);display:flex;align-items:center;color:var(--fk-input-placeholder-color, var(--fk-color-muted, #8a98a8));z-index:1}.fk-form-field__prefix{pointer-events:none}.fk-form-field__prefix:empty,.fk-form-field__suffix:empty{display:none}.fk-form-field__prefix{left:var(--fk-input-affix-offset, .75rem)}.fk-form-field__suffix{right:var(--fk-input-affix-offset, .75rem)}.fk-form-field__subscript{display:var(--fk-form-subscript-display, block);min-height:var(--fk-form-subscript-min-height, calc(var(--fk-form-hint-font-size, var(--fk-typography-small-font-size, .8125rem)) * 1.4));margin-top:var(--fk-form-subscript-gap, var(--fk-rhythm-1, .25rem))}.fk-form-field__subscript .fk-error{display:block;font-family:var(--fk-font-family-base, \"Inter\", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\");font-size:var(--fk-form-error-font-size, var(--fk-typography-small-font-size, .8125rem));line-height:1.4;color:var(--fk-form-error-color, var(--fk-color-danger, #e02424))}:host(.fk-form-field--focused) .fk-form-field__label{color:var(--fk-input-border-color-focus, var(--fk-color-primary, #0a84ff))}:host(.fk-form-field--disabled) .fk-form-field__label{opacity:var(--fk-input-disabled-opacity, .5);cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
539
570
  }
540
571
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormFieldComponent, decorators: [{
541
572
  type: Component,
542
- args: [{ selector: 'fk-form-field', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fk-form-field__wrapper\">\n @if (label() || labelRef()) {\n <div class=\"fk-form-field__label-row\">\n <label\n class=\"fk-form-field__label\"\n [attr.id]=\"labelId\"\n [attr.for]=\"control()?.id ?? null\"\n >\n @if (label()) {\n {{ label() }}\n } @else {\n <ng-content select=\"fk-label\" />\n }\n </label>\n <!-- The required marker lives OUTSIDE the <label> so it never leaks into\n the label's text/accessible name (keeping getByLabel + AT clean); the\n input's aria-required conveys the requirement to assistive tech. -->\n @if (isRequired()) {\n <span class=\"fk-form-field__required\" aria-hidden=\"true\">*</span>\n }\n </div>\n }\n\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"fk-form-field__control\"\n (click)=\"control()?.onContainerClick?.($event)\"\n >\n <!-- Rendered unconditionally, and deliberately so. Gating these on the\n directive resolving means a consumer who marks an element `fkPrefix`\n but forgets to import `FkPrefixDirective` gets no slot, and because an\n unprojected node is never placed in the DOM at all, their icon\n disappears with no error anywhere. Both wrappers are absolutely\n positioned and `:empty`-hidden, so rendering them always costs nothing\n and turns that failure into something visible. -->\n <div class=\"fk-form-field__prefix\">\n <ng-content select=\"[fkPrefix]\" />\n </div>\n <ng-content />\n <div class=\"fk-form-field__suffix\">\n <ng-content select=\"[fkSuffix]\" />\n </div>\n </div>\n\n <div class=\"fk-form-field__subscript\">\n @if (showError()) {\n @if (hasAutoError) {\n <span class=\"fk-error\" role=\"alert\" [attr.id]=\"autoErrorId\">\n {{ autoErrorMessage() }}\n </span>\n } @else {\n <ng-content select=\"fk-error\" />\n }\n } @else {\n <ng-content select=\"fk-hint\" />\n }\n </div>\n</div>\n", styles: [":host{display:block}.fk-form-field__wrapper{display:flex;flex-direction:column}.fk-form-field__label-row{display:flex;align-items:center;gap:.25em;margin-top:var(--fk-form-label-margin-top, var(--fk-rhythm-2, .5rem))}.fk-form-field__label{display:flex;align-items:center;gap:.25em;font-family:var(--fk-font-family-base, \"Inter\", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\");font-size:var(--fk-form-label-font-size, var(--fk-typography-label-font-size, .8125rem));font-weight:var(--fk-form-label-font-weight, var(--fk-typography-label-font-weight, 600));line-height:var(--fk-form-label-line-height, var(--fk-typography-label-line-height, 1.25));color:var(--fk-form-label-color, var(--fk-color-text-strong, #0b1420));cursor:default}.fk-form-field__label fk-icon{flex:none;color:var(--fk-form-label-icon-color, currentColor)}.fk-form-field__required{color:var(--fk-form-error-color, var(--fk-color-danger, #e02424))}.fk-form-field__control{position:relative;margin-top:var(--fk-form-label-gap, var(--fk-rhythm-2, .5rem))}.fk-form-field__prefix,.fk-form-field__suffix{position:absolute;top:50%;transform:translateY(-50%);display:flex;align-items:center;color:var(--fk-input-placeholder-color, var(--fk-color-muted, #8a98a8));z-index:1}.fk-form-field__prefix{pointer-events:none}.fk-form-field__prefix:empty,.fk-form-field__suffix:empty{display:none}.fk-form-field__prefix{left:var(--fk-input-affix-offset, .75rem)}.fk-form-field__suffix{right:var(--fk-input-affix-offset, .75rem)}.fk-form-field__subscript{display:var(--fk-form-subscript-display, block);min-height:var(--fk-form-subscript-min-height, calc(var(--fk-form-hint-font-size, var(--fk-typography-small-font-size, .8125rem)) * 1.4));margin-top:var(--fk-form-subscript-gap, var(--fk-rhythm-1, .25rem))}.fk-form-field__subscript .fk-error{display:block;font-family:var(--fk-font-family-base, \"Inter\", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\");font-size:var(--fk-form-error-font-size, var(--fk-typography-small-font-size, .8125rem));line-height:1.4;color:var(--fk-form-error-color, var(--fk-color-danger, #e02424))}:host(.fk-form-field--focused) .fk-form-field__label{color:var(--fk-input-border-color-focus, var(--fk-color-primary, #0a84ff))}:host(.fk-form-field--disabled) .fk-form-field__label{opacity:var(--fk-input-disabled-opacity, .5);cursor:not-allowed}\n"] }]
543
- }], ctorParameters: () => [], propDecorators: { className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], showErrorsOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "showErrorsOn", required: false }] }], submitted: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitted", required: false }] }], externalDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "externalDescribedBy", required: false }] }], fallbackControl: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackControl", required: false }] }], errorMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessages", required: false }] }], control: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FIELD_CONTROL), { isSignal: true }] }], hintRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => HintComponent), { isSignal: true }] }], errorRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ErrorComponent), { isSignal: true }] }], labelRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => LabelComponent), { isSignal: true }] }], prefixRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FK_PREFIX), { isSignal: true }] }], suffixRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FK_SUFFIX), { isSignal: true }] }], hostClass: [{
573
+ args: [{ selector: 'fk-form-field', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fk-form-field__wrapper\">\n @if (label() || labelRef()) {\n <div class=\"fk-form-field__label-row\">\n <label\n class=\"fk-form-field__label\"\n [attr.id]=\"labelId\"\n [attr.for]=\"control()?.id ?? null\"\n >\n @if (label()) {\n {{ label() }}\n } @else {\n <ng-content select=\"fk-label\" />\n }\n </label>\n <!-- The required marker lives OUTSIDE the <label> so it never leaks into\n the label's text/accessible name (keeping getByLabel + AT clean); the\n input's aria-required conveys the requirement to assistive tech. -->\n @if (isRequired()) {\n <span class=\"fk-form-field__required\" aria-hidden=\"true\">*</span>\n }\n\n <!-- Trailing content on the label's row. Outside the <label> so it is not\n activated by a label click and does not join the input's accessible\n name. Rendered unconditionally for the same reason as the prefix and\n suffix slots: an unprojected node is never placed in the DOM, so\n gating this on the directive would make a forgotten import vanish\n silently. `:empty` keeps it from taking space. -->\n <div class=\"fk-form-field__label-action\">\n <ng-content select=\"[fkLabelAction]\" />\n </div>\n </div>\n }\n\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"fk-form-field__control\"\n (click)=\"control()?.onContainerClick?.($event)\"\n >\n <!-- Rendered unconditionally, and deliberately so. Gating these on the\n directive resolving means a consumer who marks an element `fkPrefix`\n but forgets to import `FkPrefixDirective` gets no slot, and because an\n unprojected node is never placed in the DOM at all, their icon\n disappears with no error anywhere. Both wrappers are absolutely\n positioned and `:empty`-hidden, so rendering them always costs nothing\n and turns that failure into something visible. -->\n <div class=\"fk-form-field__prefix\">\n <ng-content select=\"[fkPrefix]\" />\n </div>\n <ng-content />\n <div class=\"fk-form-field__suffix\">\n <ng-content select=\"[fkSuffix]\" />\n </div>\n </div>\n\n <div class=\"fk-form-field__subscript\">\n @if (showError()) {\n @if (hasAutoError) {\n <span class=\"fk-error\" role=\"alert\" [attr.id]=\"autoErrorId\">\n {{ autoErrorMessage() }}\n </span>\n } @else {\n <ng-content select=\"fk-error\" />\n }\n } @else {\n <ng-content select=\"fk-hint\" />\n }\n </div>\n</div>\n", styles: [":host{display:block}.fk-form-field__wrapper{display:flex;flex-direction:column}.fk-form-field__label-action{margin-inline-start:auto;display:flex;align-items:baseline}.fk-form-field__label-action:empty{display:none}.fk-form-field__label-row{display:flex;align-items:center;gap:.25em;margin-top:var(--fk-form-label-margin-top, var(--fk-rhythm-2, .5rem))}.fk-form-field__label{display:flex;align-items:center;gap:.25em;font-family:var(--fk-font-family-base, \"Inter\", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\");font-size:var(--fk-form-label-font-size, var(--fk-typography-label-font-size, .8125rem));font-weight:var(--fk-form-label-font-weight, var(--fk-typography-label-font-weight, 600));line-height:var(--fk-form-label-line-height, var(--fk-typography-label-line-height, 1.25));color:var(--fk-form-label-color, var(--fk-color-text-strong, #0b1420));cursor:default}.fk-form-field__label fk-icon{flex:none;color:var(--fk-form-label-icon-color, currentColor)}.fk-form-field__required{color:var(--fk-form-error-color, var(--fk-color-danger, #e02424))}.fk-form-field__control{position:relative;margin-top:var(--fk-form-label-gap, var(--fk-rhythm-2, .5rem))}.fk-form-field__prefix,.fk-form-field__suffix{position:absolute;top:50%;transform:translateY(-50%);display:flex;align-items:center;color:var(--fk-input-placeholder-color, var(--fk-color-muted, #8a98a8));z-index:1}.fk-form-field__prefix{pointer-events:none}.fk-form-field__prefix:empty,.fk-form-field__suffix:empty{display:none}.fk-form-field__prefix{left:var(--fk-input-affix-offset, .75rem)}.fk-form-field__suffix{right:var(--fk-input-affix-offset, .75rem)}.fk-form-field__subscript{display:var(--fk-form-subscript-display, block);min-height:var(--fk-form-subscript-min-height, calc(var(--fk-form-hint-font-size, var(--fk-typography-small-font-size, .8125rem)) * 1.4));margin-top:var(--fk-form-subscript-gap, var(--fk-rhythm-1, .25rem))}.fk-form-field__subscript .fk-error{display:block;font-family:var(--fk-font-family-base, \"Inter\", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\");font-size:var(--fk-form-error-font-size, var(--fk-typography-small-font-size, .8125rem));line-height:1.4;color:var(--fk-form-error-color, var(--fk-color-danger, #e02424))}:host(.fk-form-field--focused) .fk-form-field__label{color:var(--fk-input-border-color-focus, var(--fk-color-primary, #0a84ff))}:host(.fk-form-field--disabled) .fk-form-field__label{opacity:var(--fk-input-disabled-opacity, .5);cursor:not-allowed}\n"] }]
574
+ }], ctorParameters: () => [], propDecorators: { className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], showErrorsOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "showErrorsOn", required: false }] }], submitted: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitted", required: false }] }], externalDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "externalDescribedBy", required: false }] }], fallbackControl: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackControl", required: false }] }], errorMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessages", required: false }] }], control: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FIELD_CONTROL), { isSignal: true }] }], hintRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => HintComponent), { isSignal: true }] }], errorRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ErrorComponent), { isSignal: true }] }], labelRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => LabelComponent), { isSignal: true }] }], labelActionRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FK_LABEL_ACTION), { isSignal: true }] }], prefixRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FK_PREFIX), { isSignal: true }] }], suffixRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FK_SUFFIX), { isSignal: true }] }], hostClass: [{
544
575
  type: HostBinding,
545
576
  args: ['class']
546
577
  }] } });
@@ -3719,5 +3750,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
3719
3750
  * Generated bundle index. Do not edit.
3720
3751
  */
3721
3752
 
3722
- export { AutocompleteComponent, CheckboxComponent, DEFAULT_ERROR_MESSAGES, DateInputComponent, DatetimeInputComponent, ERROR_MESSAGES, ErrorComponent, FIELD_CONTROL, FK_AUTOCOMPLETE_OPTION_TPL, FK_FILE_DROPZONE_ICON, FK_PREFIX, FK_SUFFIX, FORM, FieldArrayOutletComponent, FieldGroupComponent, FieldOutletComponent, FieldRegistry, FileDropzoneComponent, FileUploadComponent, FkAutocompleteOptionTplDirective, FkFileDropzoneIconDirective, FkPrefixDirective, FkSuffixDirective, FormComponent, FormFieldComponent, FormRendererComponent, FormSectionComponent, HintComponent, InputComponent, LabelComponent, NumberInputComponent, PasswordInputComponent, RadioComponent, SchemaParser, SectionOutletComponent, SegmentedBarComponent, SelectComponent, SliderComponent, SwitchComponent, TagInputComponent, TextareaComponent, TimeInputComponent, ValidationRegistry, getError, getFieldState, getInvalidFields, hasError, interpolateMessage, isFormValid, markAllTouched, resolveErrorMessage };
3753
+ export { AutocompleteComponent, CheckboxComponent, DEFAULT_ERROR_MESSAGES, DateInputComponent, DatetimeInputComponent, ERROR_MESSAGES, ErrorComponent, FIELD_CONTROL, FK_AUTOCOMPLETE_OPTION_TPL, FK_FILE_DROPZONE_ICON, FK_LABEL_ACTION, FK_PREFIX, FK_SUFFIX, FORM, FieldArrayOutletComponent, FieldGroupComponent, FieldOutletComponent, FieldRegistry, FileDropzoneComponent, FileUploadComponent, FkAutocompleteOptionTplDirective, FkFileDropzoneIconDirective, FkLabelActionDirective, FkPrefixDirective, FkSuffixDirective, FormComponent, FormFieldComponent, FormRendererComponent, FormSectionComponent, HintComponent, InputComponent, LabelComponent, NumberInputComponent, PasswordInputComponent, RadioComponent, SchemaParser, SectionOutletComponent, SegmentedBarComponent, SelectComponent, SliderComponent, SwitchComponent, TagInputComponent, TextareaComponent, TimeInputComponent, ValidationRegistry, getError, getFieldState, getInvalidFields, hasError, interpolateMessage, isFormValid, markAllTouched, resolveErrorMessage };
3723
3754
  //# sourceMappingURL=frame-kit-ui-ng-forms.mjs.map