feat: Page Editor UX Phase 2 - unsaved changes panel, property search, reset property, empty states
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

- ProjectEditorFacade.resetField(key): field-level revert-to-original, reusing getByPath + a new immutable setByPath (write-side counterpart, scalar/object dot-paths only).
- app-form-field gains showReset/resetLabel/(resetClicked) - a reusable per-field reset affordance, wired on branding logo/title and theme primary/background color.
- Save bar: unsaved-changes count is now clickable, expanding a field-level diff list (reuses facade.changeSummary(), already built for the Preview tab) with jump-to-section links.
- BuilderPropertySearchComponent: filters EditorSchemaService.all() by translated label/hint, jumps to the owning section - no new registry, reuses the existing schema.
- navigation-section: app-empty-state (existing component) added for empty header/footer link lists.
- Reset Section and Draft/Published badge were already implemented; not touched.
- New copy added as translation keys (en/ru/hy).
This commit is contained in:
sdarbinyan
2026-07-27 11:12:35 +04:00
parent 42f11dd8c0
commit 65c6d6f5d1
23 changed files with 432 additions and 9 deletions

View File

@@ -1,11 +1,19 @@
<div class="app-form-field">
@if (label()) {
<label class="app-form-field__label" [for]="fieldId">
{{ label() }}
@if (required()) {
<span class="app-form-field__required" aria-hidden="true">*</span>
<div class="app-form-field__label-row">
<label class="app-form-field__label" [for]="fieldId">
{{ label() }}
@if (required()) {
<span class="app-form-field__required" aria-hidden="true">*</span>
}
</label>
@if (showReset()) {
<button type="button" class="app-form-field__reset" [attr.aria-label]="resetLabel()" (click)="resetClicked.emit()">
<app-icon name="refresh" [size]="12" />
{{ resetLabel() }}
</button>
}
</label>
</div>
}
<div class="app-form-field__control">

View File

@@ -4,12 +4,40 @@
gap: var(--space-xs, 0.25rem);
}
.app-form-field__label-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-sm, 0.5rem);
}
.app-form-field__label {
font-size: var(--font-size-base, 0.875rem);
font-weight: var(--font-weight-semibold, 600);
color: var(--text-primary, #1f322d);
}
.app-form-field__reset {
display: inline-flex;
align-items: center;
gap: 0.2rem;
border: none;
background: none;
padding: 0;
color: var(--text-secondary, #6b7280);
font-size: var(--font-size-xs, 0.75rem);
cursor: pointer;
&:hover {
color: var(--primary-color, #2f6e5d);
}
&:focus-visible {
outline: 2px solid var(--primary-color, #2f6e5d);
outline-offset: 2px;
}
}
.app-form-field__required {
color: var(--error-color, #c0392b);
margin-left: 0.125rem;

View File

@@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
import { IconComponent } from '../icon/icon.component';
let nextFormFieldId = 0;
@@ -14,6 +15,7 @@ export abstract class FormFieldContext {
@Component({
selector: 'app-form-field',
standalone: true,
imports: [IconComponent],
templateUrl: './form-field.component.html',
styleUrl: './form-field.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -27,6 +29,10 @@ export class FormFieldComponent implements FormFieldContext {
/** Optional "Where is this used?" list, e.g. ['Buttons', 'Links', 'Badges']. Already-translated strings. */
readonly usedBy = input<readonly string[] | null>(null);
readonly usedByLabel = input<string | null>(null);
/** Shows a small "reset to original" button next to the label when the field has been changed. */
readonly showReset = input(false);
readonly resetLabel = input<string | null>(null);
readonly resetClicked = output<void>();
readonly fieldId = `app-form-field-${++nextFormFieldId}`;
protected readonly hintId = `${this.fieldId}-hint`;