humainflow-web/src/app/shared/nodes/node-list-field/node-list-field.ts

64 lines
2.3 KiB
TypeScript

// SPDX-FileCopyrightText: 2025-2026 Lucio Lelii <lucio.lelii@isti.cnr.it> - ISTI-CNR
// SPDX-License-Identifier: AGPL-3.0-or-later
// Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM.
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, signal } from '@angular/core';
export type NodeListFieldItem = {
index: number;
summary: string;
/** Something the item can be opened to read in full, when it has one. */
definition?: unknown | null;
};
/**
* A node parameter that is a list: its name and how many items it has, the items themselves on
* demand. Shared by every node that shows lists - blocks, the execution view, and containers once
* one has a list parameter - so they all read and behave the same.
*
* <p>Starts closed. With no items there is nothing to open, and the add button is the way in; an
* item added opens the list, so what was just added is there to see rather than only counted.
*/
@Component({
selector: 'app-node-list-field',
standalone: true,
templateUrl: './node-list-field.html',
styleUrl: './node-list-field.css',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class NodeListFieldComponent implements OnChanges {
@Input({ required: true }) label = '';
@Input() items: NodeListFieldItem[] = [];
/** Read-only views - an execution - show the items and offer nothing to change them. */
@Input() readonly = false;
@Output() readonly add = new EventEmitter<void>();
@Output() readonly edit = new EventEmitter<number>();
@Output() readonly remove = new EventEmitter<number>();
@Output() readonly view = new EventEmitter<unknown>();
readonly expanded = signal(false);
ngOnChanges(changes: SimpleChanges): void {
const change = changes['items'];
if (!change || change.firstChange) return;
const before = Array.isArray(change.previousValue) ? change.previousValue.length : 0;
const after = this.items?.length ?? 0;
if (after > before) this.expanded.set(true);
if (after === 0) this.expanded.set(false);
}
toggle(event: Event) {
event.preventDefault();
event.stopPropagation();
if (!this.items.length) return;
this.expanded.update((open) => !open);
}
emit<T>(emitter: EventEmitter<T>, value: T, event: Event) {
event.preventDefault();
event.stopPropagation();
emitter.emit(value);
}
}