core/forms/input-group-editable.component.ts
Inline editable input field that displays a value as plain text and switches to an edit mode with save and cancel actions.
Use this component when a value should be editable in place without navigating
away from the current view. Supports small and large size variants and
announces state changes to screen readers via an aria-live region.
Implements ControlValueAccessor so it can be used with ngModel,
formControl, or formControlName. The value is propagated to the form
control only when the user confirms an edit (save), not on every keystroke.
When noSaveButton is true, every keystroke propagates instead.
The model update (onChange) happens before the (save) event fires, so
(save) handlers can read the committed value directly from the bound form
control or ngModel.
Supports synchronous and asynchronous validators via the validators and
asyncValidators inputs. Validation errors are shown while the user is
editing; saving is blocked when the typed value is invalid or a validator
is pending.
Custom error messages can be projected as <c8y-message> children:
<c8y-input-group-editable
[ngModel]="name"
(ngModelChange)="saveName($event)"
[validators]="[myValidator]"
ariaLabel="Name"
>
<c8y-message name="myError" text="Value is invalid."></c8y-message>
</c8y-input-group-editable>
ControlValueAccessor
OnInit
AfterContentInit
OnDestroy
| changeDetection | ChangeDetectionStrategy.OnPush |
| host | { |
| providers |
)
|
| selector | c8y-input-group-editable |
| standalone | true |
| imports |
C8yTranslatePipe
|
| templateUrl | ./input-group-editable.component.html |
<c8y-form-group
[class]="formGroupClass()"
[status]="!!errors ? 'error' : ''"
>
<div
class="input-group input-group-editable"
[class.input-group-sm]="size() === 'sm'"
[class.input-group-lg]="size() === 'lg'"
[class.input-group-editable--no-actions]="noSaveButton()"
[attr.aria-label]="ariaLabel() | translate"
role="group"
>
@if (multiline()) {
<textarea
class="form-control no-resize"
[style.max-height]="maxHeight() || null"
[attr.aria-label]="ariaLabel() | translate"
data-cy="c8y-input-group-editable--input"
#inputEl
[attr.maxlength]="maxLength()"
[attr.name]="name() || null"
[placeholder]="(placeholder() | translate) || ''"
[formControl]="internalControl"
(keydown.control.enter)="noSaveButton() ? null : onSave()"
(keydown.escape)="onEscape($event)"
c8y-textarea-autoresize
></textarea>
} @else {
<input
class="form-control"
[attr.aria-label]="ariaLabel() | translate"
data-cy="c8y-input-group-editable--input"
#inputEl
[attr.maxlength]="maxLength()"
[attr.name]="name() || null"
[attr.autocomplete]="autocomplete()"
[placeholder]="(placeholder() | translate) || ''"
[formControl]="internalControl"
(keydown.enter)="noSaveButton() ? null : onSave()"
(keydown.escape)="onEscape($event)"
data-1p-ignore
data-lpignore="true"
data-bwignore
/>
}
<!-- Edit mode indicator hook used by _input-groups.scss -->
<span aria-hidden="true"></span>
@if (!internalControl.disabled && !noSaveButton()) {
<div class="input-group-btn min-width-0">
<button
class="btn btn-clean"
[title]="'Cancel' | translate"
[attr.aria-label]="'Cancel' | translate"
type="button"
(click)="onCancel()"
>
<i
c8yIcon="times"
aria-hidden="true"
></i>
</button>
<button
class="btn btn-clean text-primary"
[title]="'Save' | translate"
[attr.aria-label]="'Save' | translate"
type="button"
[disabled]="isSaveDisabled"
(click)="onSave()"
>
<i
c8yIcon="check"
aria-hidden="true"
></i>
</button>
</div>
}
<span
class="sr-only"
aria-live="polite"
aria-atomic="true"
>{{ liveMessage | translate }}</span
>
</div>
<c8y-messages
[show]="errors"
[additionalMessages]="customErrorMessages"
></c8y-messages>
</c8y-form-group>