@frame-kit/ui-ng 0.1.8 → 0.1.9

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.
@@ -1,10 +1,40 @@
1
1
  import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle, CdkDragPlaceholder } from '@angular/cdk/drag-drop';
2
+ import { NgTemplateOutlet } from '@angular/common';
2
3
  import * as i0 from '@angular/core';
3
- import { model, input, output, signal, computed, HostBinding, ChangeDetectionStrategy, Component } from '@angular/core';
4
+ import { inject, TemplateRef, Directive, model, input, output, contentChild, signal, computed, HostBinding, ChangeDetectionStrategy, Component } from '@angular/core';
4
5
  import * as i1 from '@angular/forms';
5
6
  import { FormsModule } from '@angular/forms';
6
7
  import { ButtonComponent, FkButtonIconStartDirective } from '@frame-kit/ui-ng/ui/button';
7
8
 
9
+ /**
10
+ * Marks an `<ng-template>` as the per-row renderer for `fk-list-editor`. When
11
+ * present, the editor renders this template for each item instead of its
12
+ * default editable input + remove button, so the consumer owns the row markup
13
+ * (scheme tag, status, custom actions, …). The add-input, empty state, and
14
+ * optional drag/order chrome are still provided by the editor.
15
+ *
16
+ * ```html
17
+ * <fk-list-editor [items]="origins">
18
+ * <ng-template fkListEditorItem let-origin let-remove="remove">
19
+ * <span>{{ origin }}</span>
20
+ * <button (click)="remove()">Remove</button>
21
+ * </ng-template>
22
+ * </fk-list-editor>
23
+ * ```
24
+ */
25
+ class FkListEditorItemDirective {
26
+ templateRef = inject((TemplateRef));
27
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FkListEditorItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
28
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: FkListEditorItemDirective, isStandalone: true, selector: "[fkListEditorItem]", ngImport: i0 });
29
+ }
30
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FkListEditorItemDirective, decorators: [{
31
+ type: Directive,
32
+ args: [{
33
+ selector: '[fkListEditorItem]',
34
+ standalone: true,
35
+ }]
36
+ }] });
37
+
8
38
  class ListEditorComponent {
9
39
  // ===== INPUTS =====
10
40
  /** The current list of string items; two-way bindable via `[(items)]`. */
@@ -34,6 +64,13 @@ class ListEditorComponent {
34
64
  itemAdded = output();
35
65
  /** Fires when an item is removed, emitting the removed value and its former index. */
36
66
  itemRemoved = output();
67
+ // ===== CONTENT =====
68
+ /**
69
+ * Optional custom row template (`*fkListEditorItem`). When provided, each
70
+ * item renders through it instead of the default editable input + remove
71
+ * button.
72
+ */
73
+ itemTemplate = contentChild(FkListEditorItemDirective, ...(ngDevMode ? [{ debugName: "itemTemplate" }] : /* istanbul ignore next */ []));
37
74
  // ===== LOCAL STATE =====
38
75
  newItemValue = signal('', ...(ngDevMode ? [{ debugName: "newItemValue" }] : /* istanbul ignore next */ []));
39
76
  // ===== COMPUTED =====
@@ -100,6 +137,16 @@ class ListEditorComponent {
100
137
  isLocked(item) {
101
138
  return this.lockedItems().includes(item.trim().toLowerCase());
102
139
  }
140
+ /** Builds the `let-` context for a custom row template. */
141
+ contextFor(item, index) {
142
+ return {
143
+ $implicit: item,
144
+ index,
145
+ locked: this.isLocked(item),
146
+ remove: () => this.removeItem(index),
147
+ update: (value) => this.updateItem(index, value),
148
+ };
149
+ }
103
150
  onKeydown(event) {
104
151
  if (event.key === 'Enter') {
105
152
  event.preventDefault();
@@ -107,20 +154,21 @@ class ListEditorComponent {
107
154
  }
108
155
  }
109
156
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ListEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
110
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ListEditorComponent, isStandalone: true, selector: "fk-list-editor", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, addLabel: { classPropertyName: "addLabel", publicName: "addLabel", isSignal: true, isRequired: false, transformFunction: null }, orderable: { classPropertyName: "orderable", publicName: "orderable", isSignal: true, isRequired: false, transformFunction: null }, showOrder: { classPropertyName: "showOrder", publicName: "showOrder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, lockedItems: { classPropertyName: "lockedItems", publicName: "lockedItems", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { items: "itemsChange", itemsChange: "itemsChange", itemAdded: "itemAdded", itemRemoved: "itemRemoved" }, host: { properties: { "class": "this.hostClass" } }, ngImport: i0, template: "<div\n cdkDropList\n [cdkDropListDisabled]=\"disabled() || !orderable()\"\n (cdkDropListDropped)=\"onDrop($event)\"\n class=\"fk-list-editor__list\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n @for (item of items(); track $index) {\n <div\n cdkDrag\n [cdkDragDisabled]=\"disabled() || !orderable()\"\n class=\"fk-list-editor__item\"\n >\n @if (orderable()) {\n <span class=\"fk-list-editor__handle\" cdkDragHandle>\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <circle cx=\"9\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n </svg>\n </span>\n }\n\n @if (showOrder()) {\n <span class=\"fk-list-editor__order\">{{ $index + 1 }}</span>\n }\n\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [value]=\"item\"\n [disabled]=\"disabled() || isLocked(item)\"\n (input)=\"updateItem($index, $any($event.target).value)\"\n />\n\n <button\n class=\"fk-list-editor__remove\"\n type=\"button\"\n [disabled]=\"disabled() || isLocked(item)\"\n [title]=\"isLocked(item) ? 'This item cannot be removed' : 'Remove item'\"\n (click)=\"removeItem($index)\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n\n <div *cdkDragPlaceholder class=\"fk-list-editor__placeholder\"></div>\n </div>\n }\n</div>\n\n@if (items().length === 0 && emptyMessage()) {\n <p class=\"fk-list-editor__empty\">{{ emptyMessage() }}</p>\n}\n\n<div class=\"fk-list-editor__add\">\n <div class=\"fk-list-editor__add-input\">\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [ngModel]=\"newItemValue()\"\n (ngModelChange)=\"newItemValue.set($event)\"\n (keydown)=\"onKeydown($event)\"\n />\n\n @if (newItemValue() && !disabled()) {\n <button\n class=\"fk-list-editor__add-clear\"\n type=\"button\"\n aria-label=\"Clear input\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"newItemValue.set('')\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n </div>\n\n <fk-button\n variant=\"outline\"\n size=\"sm\"\n [disabled]=\"!canAdd() || disabled()\"\n (click)=\"addItem()\"\n >\n <svg\n fkButtonIconStart\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 5v14M5 12h14\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n {{ addLabel() }}\n </fk-button>\n</div>\n\n@if (hasDuplicates()) {\n <p class=\"fk-list-editor__error\">Item names must be unique.</p>\n}\n", styles: [":host{display:block}.fk-list-editor__list{display:flex;flex-direction:column;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem))}.fk-list-editor__item{display:flex;align-items:center;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem));padding:var(--fk-list-editor-item-padding, var(--fk-rhythm-2, .5rem) var(--fk-rhythm-3, .75rem));background:var(--fk-list-editor-item-bg, var(--fk-color-surface, #ffffff));border:1px solid var(--fk-list-editor-item-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-item-radius, var(--fk-radius-md, .5rem));transition:box-shadow .15s ease}.fk-list-editor__item:hover{box-shadow:var(--fk-list-editor-item-hover-shadow, 0 1px 3px rgba(0, 0, 0, .06))}.fk-list-editor__icon{width:var(--fk-list-editor-icon-size, 1rem);height:var(--fk-list-editor-icon-size, 1rem)}.fk-list-editor__handle{display:flex;cursor:grab;color:var(--fk-list-editor-handle-color, var(--fk-color-muted, #8a98a8));padding:var(--fk-list-editor-handle-padding, var(--fk-rhythm-1, .25rem))}.fk-list-editor__handle:active{cursor:grabbing}.fk-list-editor__order{display:flex;align-items:center;justify-content:center;width:var(--fk-list-editor-order-size, 1.5rem);height:var(--fk-list-editor-order-size, 1.5rem);border-radius:var(--fk-list-editor-order-radius, 50%);background:var(--fk-list-editor-order-bg, var(--fk-color-primary-light, #e0f2fe));color:var(--fk-list-editor-order-color, var(--fk-color-primary, #0a84ff));font-size:var(--fk-list-editor-order-font-size, .75rem);font-weight:var(--fk-list-editor-order-font-weight, var(--fk-font-weight-semibold, 600));flex-shrink:0}.fk-list-editor__input{flex:1;min-width:0;padding:var(--fk-list-editor-input-padding, var(--fk-rhythm-2, .5rem) var(--fk-rhythm-3, .75rem));border:1px solid var(--fk-list-editor-input-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-input-radius, var(--fk-radius-md, .5rem));font-size:var(--fk-list-editor-input-font-size, .875rem);color:var(--fk-list-editor-input-color, var(--fk-color-text, #1f2d3d));background:var(--fk-list-editor-input-bg, var(--fk-color-surface, #ffffff));outline:none;transition:border-color .15s ease}.fk-list-editor__input:focus{border-color:var(--fk-list-editor-input-focus-border-color, var(--fk-color-primary, #0a84ff))}.fk-list-editor__input:disabled{opacity:var(--fk-list-editor-disabled-opacity, var(--fk-input-disabled-opacity, .5));cursor:not-allowed}.fk-list-editor__remove{display:flex;align-items:center;padding:var(--fk-list-editor-remove-padding, var(--fk-rhythm-1, .25rem));border:none;background:transparent;cursor:pointer;color:var(--fk-list-editor-remove-color, var(--fk-color-muted, #8a98a8));transition:color .15s ease}.fk-list-editor__remove:hover:not(:disabled){color:var(--fk-list-editor-remove-hover-color, var(--fk-color-danger, #e02424))}.fk-list-editor__remove:focus-visible{outline:none;box-shadow:var(--fk-list-editor-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-list-editor__remove:disabled{opacity:.35;cursor:not-allowed}.fk-list-editor__placeholder{height:var(--fk-list-editor-placeholder-height, 3rem);border:2px dashed var(--fk-list-editor-placeholder-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-item-radius, var(--fk-radius-md, .5rem));background:var(--fk-list-editor-placeholder-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-list-editor__empty{padding:var(--fk-list-editor-empty-padding, var(--fk-rhythm-4, 1rem));text-align:var(--fk-list-editor-empty-text-align, left);color:var(--fk-list-editor-empty-color, var(--fk-color-muted, #8a98a8));font-size:var(--fk-list-editor-empty-font-size, var(--fk-typography-small-font-size, .875rem))}.fk-list-editor__add{display:flex;align-items:center;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem));margin-top:var(--fk-list-editor-add-gap, var(--fk-rhythm-3, .75rem))}.fk-list-editor__add-input{position:relative;display:flex;flex:1;min-width:0}.fk-list-editor__add-input .fk-list-editor__input{padding-right:var(--fk-list-editor-add-clear-reserve, var(--fk-rhythm-7, 1.75rem))}.fk-list-editor__add-clear{position:absolute;top:50%;right:var(--fk-list-editor-add-clear-offset, var(--fk-rhythm-2, .5rem));transform:translateY(-50%);display:flex;align-items:center;justify-content:center;padding:var(--fk-list-editor-add-clear-padding, .125rem);border:0;background:transparent;border-radius:var(--fk-list-editor-add-clear-radius, var(--fk-radius-sm, .25rem));color:var(--fk-list-editor-remove-color, var(--fk-color-muted, #8a98a8));cursor:pointer;transition:color .15s ease}.fk-list-editor__add-clear:hover{color:var(--fk-list-editor-remove-hover-color, var(--fk-color-danger, #e02424))}.fk-list-editor__add-clear:focus-visible{outline:none;box-shadow:var(--fk-list-editor-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-list-editor__error{margin-top:var(--fk-list-editor-error-gap, var(--fk-rhythm-2, .5rem));color:var(--fk-list-editor-error-color, var(--fk-color-danger, #e02424));font-size:var(--fk-list-editor-error-font-size, var(--fk-typography-small-font-size, .875rem))}:host.fk-list-editor--sm .fk-list-editor__input{font-size:var(--fk-list-editor-input-font-size-sm, .8125rem);padding:var(--fk-list-editor-input-padding-sm, var(--fk-rhythm-1, .25rem) var(--fk-rhythm-2, .5rem))}:host.fk-list-editor--sm .fk-list-editor__item{padding:var(--fk-list-editor-item-padding-sm, var(--fk-rhythm-1, .25rem) var(--fk-rhythm-2, .5rem))}:host.fk-list-editor--disabled{opacity:var(--fk-list-editor-disabled-opacity, var(--fk-input-disabled-opacity, .5));pointer-events:none}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.fk-list-editor__list.cdk-drop-list-dragging .fk-list-editor__item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drag-preview.fk-list-editor__item{box-shadow:var(--fk-list-editor-drag-shadow, 0 4px 12px rgba(0, 0, 0, .12));border-color:var(--fk-list-editor-drag-border-color, var(--fk-color-primary, #0a84ff))}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "component", type: ButtonComponent, selector: "fk-button", inputs: ["variant", "size", "loading", "disabled", "type", "fullWidth", "id", "className", "ariaLabel", "ariaDescribedBy"] }, { kind: "directive", type: FkButtonIconStartDirective, selector: "[fkButtonIconStart]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
157
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ListEditorComponent, isStandalone: true, selector: "fk-list-editor", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, addLabel: { classPropertyName: "addLabel", publicName: "addLabel", isSignal: true, isRequired: false, transformFunction: null }, orderable: { classPropertyName: "orderable", publicName: "orderable", isSignal: true, isRequired: false, transformFunction: null }, showOrder: { classPropertyName: "showOrder", publicName: "showOrder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, lockedItems: { classPropertyName: "lockedItems", publicName: "lockedItems", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { items: "itemsChange", itemsChange: "itemsChange", itemAdded: "itemAdded", itemRemoved: "itemRemoved" }, host: { properties: { "class": "this.hostClass" } }, queries: [{ propertyName: "itemTemplate", first: true, predicate: FkListEditorItemDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div\n cdkDropList\n [cdkDropListDisabled]=\"disabled() || !orderable()\"\n (cdkDropListDropped)=\"onDrop($event)\"\n class=\"fk-list-editor__list\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n @for (item of items(); track $index) {\n <div\n cdkDrag\n [cdkDragDisabled]=\"disabled() || !orderable()\"\n class=\"fk-list-editor__item\"\n >\n @if (orderable()) {\n <span class=\"fk-list-editor__handle\" cdkDragHandle>\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <circle cx=\"9\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n </svg>\n </span>\n }\n\n @if (showOrder()) {\n <span class=\"fk-list-editor__order\">{{ $index + 1 }}</span>\n }\n\n @if (itemTemplate(); as def) {\n <div class=\"fk-list-editor__item-custom\">\n <ng-container\n [ngTemplateOutlet]=\"def.templateRef\"\n [ngTemplateOutletContext]=\"contextFor(item, $index)\"\n />\n </div>\n } @else {\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [value]=\"item\"\n [disabled]=\"disabled() || isLocked(item)\"\n (input)=\"updateItem($index, $any($event.target).value)\"\n />\n\n <button\n class=\"fk-list-editor__remove\"\n type=\"button\"\n [disabled]=\"disabled() || isLocked(item)\"\n [title]=\"\n isLocked(item) ? 'This item cannot be removed' : 'Remove item'\n \"\n (click)=\"removeItem($index)\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n\n <div *cdkDragPlaceholder class=\"fk-list-editor__placeholder\"></div>\n </div>\n }\n</div>\n\n@if (items().length === 0 && emptyMessage()) {\n <p class=\"fk-list-editor__empty\">{{ emptyMessage() }}</p>\n}\n\n<div class=\"fk-list-editor__add\">\n <div class=\"fk-list-editor__add-input\">\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [ngModel]=\"newItemValue()\"\n (ngModelChange)=\"newItemValue.set($event)\"\n (keydown)=\"onKeydown($event)\"\n />\n\n @if (newItemValue() && !disabled()) {\n <button\n class=\"fk-list-editor__add-clear\"\n type=\"button\"\n aria-label=\"Clear input\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"newItemValue.set('')\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n </div>\n\n <fk-button\n variant=\"outline\"\n size=\"sm\"\n [disabled]=\"!canAdd() || disabled()\"\n (click)=\"addItem()\"\n >\n <svg\n fkButtonIconStart\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 5v14M5 12h14\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n {{ addLabel() }}\n </fk-button>\n</div>\n\n@if (hasDuplicates()) {\n <p class=\"fk-list-editor__error\">Item names must be unique.</p>\n}\n", styles: [":host{display:block}.fk-list-editor__list{display:flex;flex-direction:column;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem))}.fk-list-editor__item{display:flex;align-items:center;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem));padding:var(--fk-list-editor-item-padding, var(--fk-rhythm-2, .5rem) var(--fk-rhythm-3, .75rem));background:var(--fk-list-editor-item-bg, var(--fk-color-surface, #ffffff));border:1px solid var(--fk-list-editor-item-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-item-radius, var(--fk-radius-md, .5rem));transition:box-shadow .15s ease}.fk-list-editor__item:hover{box-shadow:var(--fk-list-editor-item-hover-shadow, 0 1px 3px rgba(0, 0, 0, .06))}.fk-list-editor__icon{width:var(--fk-list-editor-icon-size, 1rem);height:var(--fk-list-editor-icon-size, 1rem)}.fk-list-editor__handle{display:flex;cursor:grab;color:var(--fk-list-editor-handle-color, var(--fk-color-muted, #8a98a8));padding:var(--fk-list-editor-handle-padding, var(--fk-rhythm-1, .25rem))}.fk-list-editor__handle:active{cursor:grabbing}.fk-list-editor__order{display:flex;align-items:center;justify-content:center;width:var(--fk-list-editor-order-size, 1.5rem);height:var(--fk-list-editor-order-size, 1.5rem);border-radius:var(--fk-list-editor-order-radius, 50%);background:var(--fk-list-editor-order-bg, var(--fk-color-primary-light, #e0f2fe));color:var(--fk-list-editor-order-color, var(--fk-color-primary, #0a84ff));font-size:var(--fk-list-editor-order-font-size, .75rem);font-weight:var(--fk-list-editor-order-font-weight, var(--fk-font-weight-semibold, 600));flex-shrink:0}.fk-list-editor__item-custom{flex:1;min-width:0}.fk-list-editor__input{flex:1;min-width:0;padding:var(--fk-list-editor-input-padding, var(--fk-rhythm-2, .5rem) var(--fk-rhythm-3, .75rem));border:1px solid var(--fk-list-editor-input-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-input-radius, var(--fk-radius-md, .5rem));font-size:var(--fk-list-editor-input-font-size, .875rem);color:var(--fk-list-editor-input-color, var(--fk-color-text, #1f2d3d));background:var(--fk-list-editor-input-bg, var(--fk-color-surface, #ffffff));outline:none;transition:border-color .15s ease}.fk-list-editor__input:focus{border-color:var(--fk-list-editor-input-focus-border-color, var(--fk-color-primary, #0a84ff))}.fk-list-editor__input:disabled{opacity:var(--fk-list-editor-disabled-opacity, var(--fk-input-disabled-opacity, .5));cursor:not-allowed}.fk-list-editor__remove{display:flex;align-items:center;padding:var(--fk-list-editor-remove-padding, var(--fk-rhythm-1, .25rem));border:none;background:transparent;cursor:pointer;color:var(--fk-list-editor-remove-color, var(--fk-color-muted, #8a98a8));transition:color .15s ease}.fk-list-editor__remove:hover:not(:disabled){color:var(--fk-list-editor-remove-hover-color, var(--fk-color-danger, #e02424))}.fk-list-editor__remove:focus-visible{outline:none;box-shadow:var(--fk-list-editor-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-list-editor__remove:disabled{opacity:.35;cursor:not-allowed}.fk-list-editor__placeholder{height:var(--fk-list-editor-placeholder-height, 3rem);border:2px dashed var(--fk-list-editor-placeholder-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-item-radius, var(--fk-radius-md, .5rem));background:var(--fk-list-editor-placeholder-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-list-editor__empty{padding:var(--fk-list-editor-empty-padding, var(--fk-rhythm-4, 1rem));text-align:var(--fk-list-editor-empty-text-align, left);color:var(--fk-list-editor-empty-color, var(--fk-color-muted, #8a98a8));font-size:var(--fk-list-editor-empty-font-size, var(--fk-typography-small-font-size, .875rem))}.fk-list-editor__add{display:flex;align-items:center;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem));margin-top:var(--fk-list-editor-add-gap, var(--fk-rhythm-3, .75rem))}.fk-list-editor__add-input{position:relative;display:flex;flex:1;min-width:0}.fk-list-editor__add-input .fk-list-editor__input{padding-right:var(--fk-list-editor-add-clear-reserve, var(--fk-rhythm-7, 1.75rem))}.fk-list-editor__add-clear{position:absolute;top:50%;right:var(--fk-list-editor-add-clear-offset, var(--fk-rhythm-2, .5rem));transform:translateY(-50%);display:flex;align-items:center;justify-content:center;padding:var(--fk-list-editor-add-clear-padding, .125rem);border:0;background:transparent;border-radius:var(--fk-list-editor-add-clear-radius, var(--fk-radius-sm, .25rem));color:var(--fk-list-editor-remove-color, var(--fk-color-muted, #8a98a8));cursor:pointer;transition:color .15s ease}.fk-list-editor__add-clear:hover{color:var(--fk-list-editor-remove-hover-color, var(--fk-color-danger, #e02424))}.fk-list-editor__add-clear:focus-visible{outline:none;box-shadow:var(--fk-list-editor-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-list-editor__error{margin-top:var(--fk-list-editor-error-gap, var(--fk-rhythm-2, .5rem));color:var(--fk-list-editor-error-color, var(--fk-color-danger, #e02424));font-size:var(--fk-list-editor-error-font-size, var(--fk-typography-small-font-size, .875rem))}:host.fk-list-editor--sm .fk-list-editor__input{font-size:var(--fk-list-editor-input-font-size-sm, .8125rem);padding:var(--fk-list-editor-input-padding-sm, var(--fk-rhythm-1, .25rem) var(--fk-rhythm-2, .5rem))}:host.fk-list-editor--sm .fk-list-editor__item{padding:var(--fk-list-editor-item-padding-sm, var(--fk-rhythm-1, .25rem) var(--fk-rhythm-2, .5rem))}:host.fk-list-editor--disabled{opacity:var(--fk-list-editor-disabled-opacity, var(--fk-input-disabled-opacity, .5));pointer-events:none}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.fk-list-editor__list.cdk-drop-list-dragging .fk-list-editor__item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drag-preview.fk-list-editor__item{box-shadow:var(--fk-list-editor-drag-shadow, 0 4px 12px rgba(0, 0, 0, .12));border-color:var(--fk-list-editor-drag-border-color, var(--fk-color-primary, #0a84ff))}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "component", type: ButtonComponent, selector: "fk-button", inputs: ["variant", "size", "loading", "disabled", "type", "fullWidth", "id", "className", "ariaLabel", "ariaDescribedBy"] }, { kind: "directive", type: FkButtonIconStartDirective, selector: "[fkButtonIconStart]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
111
158
  }
112
159
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ListEditorComponent, decorators: [{
113
160
  type: Component,
114
161
  args: [{ selector: 'fk-list-editor', standalone: true, imports: [
115
162
  FormsModule,
163
+ NgTemplateOutlet,
116
164
  CdkDropList,
117
165
  CdkDrag,
118
166
  CdkDragHandle,
119
167
  CdkDragPlaceholder,
120
168
  ButtonComponent,
121
169
  FkButtonIconStartDirective,
122
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n cdkDropList\n [cdkDropListDisabled]=\"disabled() || !orderable()\"\n (cdkDropListDropped)=\"onDrop($event)\"\n class=\"fk-list-editor__list\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n @for (item of items(); track $index) {\n <div\n cdkDrag\n [cdkDragDisabled]=\"disabled() || !orderable()\"\n class=\"fk-list-editor__item\"\n >\n @if (orderable()) {\n <span class=\"fk-list-editor__handle\" cdkDragHandle>\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <circle cx=\"9\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n </svg>\n </span>\n }\n\n @if (showOrder()) {\n <span class=\"fk-list-editor__order\">{{ $index + 1 }}</span>\n }\n\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [value]=\"item\"\n [disabled]=\"disabled() || isLocked(item)\"\n (input)=\"updateItem($index, $any($event.target).value)\"\n />\n\n <button\n class=\"fk-list-editor__remove\"\n type=\"button\"\n [disabled]=\"disabled() || isLocked(item)\"\n [title]=\"isLocked(item) ? 'This item cannot be removed' : 'Remove item'\"\n (click)=\"removeItem($index)\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n\n <div *cdkDragPlaceholder class=\"fk-list-editor__placeholder\"></div>\n </div>\n }\n</div>\n\n@if (items().length === 0 && emptyMessage()) {\n <p class=\"fk-list-editor__empty\">{{ emptyMessage() }}</p>\n}\n\n<div class=\"fk-list-editor__add\">\n <div class=\"fk-list-editor__add-input\">\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [ngModel]=\"newItemValue()\"\n (ngModelChange)=\"newItemValue.set($event)\"\n (keydown)=\"onKeydown($event)\"\n />\n\n @if (newItemValue() && !disabled()) {\n <button\n class=\"fk-list-editor__add-clear\"\n type=\"button\"\n aria-label=\"Clear input\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"newItemValue.set('')\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n </div>\n\n <fk-button\n variant=\"outline\"\n size=\"sm\"\n [disabled]=\"!canAdd() || disabled()\"\n (click)=\"addItem()\"\n >\n <svg\n fkButtonIconStart\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 5v14M5 12h14\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n {{ addLabel() }}\n </fk-button>\n</div>\n\n@if (hasDuplicates()) {\n <p class=\"fk-list-editor__error\">Item names must be unique.</p>\n}\n", styles: [":host{display:block}.fk-list-editor__list{display:flex;flex-direction:column;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem))}.fk-list-editor__item{display:flex;align-items:center;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem));padding:var(--fk-list-editor-item-padding, var(--fk-rhythm-2, .5rem) var(--fk-rhythm-3, .75rem));background:var(--fk-list-editor-item-bg, var(--fk-color-surface, #ffffff));border:1px solid var(--fk-list-editor-item-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-item-radius, var(--fk-radius-md, .5rem));transition:box-shadow .15s ease}.fk-list-editor__item:hover{box-shadow:var(--fk-list-editor-item-hover-shadow, 0 1px 3px rgba(0, 0, 0, .06))}.fk-list-editor__icon{width:var(--fk-list-editor-icon-size, 1rem);height:var(--fk-list-editor-icon-size, 1rem)}.fk-list-editor__handle{display:flex;cursor:grab;color:var(--fk-list-editor-handle-color, var(--fk-color-muted, #8a98a8));padding:var(--fk-list-editor-handle-padding, var(--fk-rhythm-1, .25rem))}.fk-list-editor__handle:active{cursor:grabbing}.fk-list-editor__order{display:flex;align-items:center;justify-content:center;width:var(--fk-list-editor-order-size, 1.5rem);height:var(--fk-list-editor-order-size, 1.5rem);border-radius:var(--fk-list-editor-order-radius, 50%);background:var(--fk-list-editor-order-bg, var(--fk-color-primary-light, #e0f2fe));color:var(--fk-list-editor-order-color, var(--fk-color-primary, #0a84ff));font-size:var(--fk-list-editor-order-font-size, .75rem);font-weight:var(--fk-list-editor-order-font-weight, var(--fk-font-weight-semibold, 600));flex-shrink:0}.fk-list-editor__input{flex:1;min-width:0;padding:var(--fk-list-editor-input-padding, var(--fk-rhythm-2, .5rem) var(--fk-rhythm-3, .75rem));border:1px solid var(--fk-list-editor-input-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-input-radius, var(--fk-radius-md, .5rem));font-size:var(--fk-list-editor-input-font-size, .875rem);color:var(--fk-list-editor-input-color, var(--fk-color-text, #1f2d3d));background:var(--fk-list-editor-input-bg, var(--fk-color-surface, #ffffff));outline:none;transition:border-color .15s ease}.fk-list-editor__input:focus{border-color:var(--fk-list-editor-input-focus-border-color, var(--fk-color-primary, #0a84ff))}.fk-list-editor__input:disabled{opacity:var(--fk-list-editor-disabled-opacity, var(--fk-input-disabled-opacity, .5));cursor:not-allowed}.fk-list-editor__remove{display:flex;align-items:center;padding:var(--fk-list-editor-remove-padding, var(--fk-rhythm-1, .25rem));border:none;background:transparent;cursor:pointer;color:var(--fk-list-editor-remove-color, var(--fk-color-muted, #8a98a8));transition:color .15s ease}.fk-list-editor__remove:hover:not(:disabled){color:var(--fk-list-editor-remove-hover-color, var(--fk-color-danger, #e02424))}.fk-list-editor__remove:focus-visible{outline:none;box-shadow:var(--fk-list-editor-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-list-editor__remove:disabled{opacity:.35;cursor:not-allowed}.fk-list-editor__placeholder{height:var(--fk-list-editor-placeholder-height, 3rem);border:2px dashed var(--fk-list-editor-placeholder-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-item-radius, var(--fk-radius-md, .5rem));background:var(--fk-list-editor-placeholder-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-list-editor__empty{padding:var(--fk-list-editor-empty-padding, var(--fk-rhythm-4, 1rem));text-align:var(--fk-list-editor-empty-text-align, left);color:var(--fk-list-editor-empty-color, var(--fk-color-muted, #8a98a8));font-size:var(--fk-list-editor-empty-font-size, var(--fk-typography-small-font-size, .875rem))}.fk-list-editor__add{display:flex;align-items:center;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem));margin-top:var(--fk-list-editor-add-gap, var(--fk-rhythm-3, .75rem))}.fk-list-editor__add-input{position:relative;display:flex;flex:1;min-width:0}.fk-list-editor__add-input .fk-list-editor__input{padding-right:var(--fk-list-editor-add-clear-reserve, var(--fk-rhythm-7, 1.75rem))}.fk-list-editor__add-clear{position:absolute;top:50%;right:var(--fk-list-editor-add-clear-offset, var(--fk-rhythm-2, .5rem));transform:translateY(-50%);display:flex;align-items:center;justify-content:center;padding:var(--fk-list-editor-add-clear-padding, .125rem);border:0;background:transparent;border-radius:var(--fk-list-editor-add-clear-radius, var(--fk-radius-sm, .25rem));color:var(--fk-list-editor-remove-color, var(--fk-color-muted, #8a98a8));cursor:pointer;transition:color .15s ease}.fk-list-editor__add-clear:hover{color:var(--fk-list-editor-remove-hover-color, var(--fk-color-danger, #e02424))}.fk-list-editor__add-clear:focus-visible{outline:none;box-shadow:var(--fk-list-editor-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-list-editor__error{margin-top:var(--fk-list-editor-error-gap, var(--fk-rhythm-2, .5rem));color:var(--fk-list-editor-error-color, var(--fk-color-danger, #e02424));font-size:var(--fk-list-editor-error-font-size, var(--fk-typography-small-font-size, .875rem))}:host.fk-list-editor--sm .fk-list-editor__input{font-size:var(--fk-list-editor-input-font-size-sm, .8125rem);padding:var(--fk-list-editor-input-padding-sm, var(--fk-rhythm-1, .25rem) var(--fk-rhythm-2, .5rem))}:host.fk-list-editor--sm .fk-list-editor__item{padding:var(--fk-list-editor-item-padding-sm, var(--fk-rhythm-1, .25rem) var(--fk-rhythm-2, .5rem))}:host.fk-list-editor--disabled{opacity:var(--fk-list-editor-disabled-opacity, var(--fk-input-disabled-opacity, .5));pointer-events:none}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.fk-list-editor__list.cdk-drop-list-dragging .fk-list-editor__item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drag-preview.fk-list-editor__item{box-shadow:var(--fk-list-editor-drag-shadow, 0 4px 12px rgba(0, 0, 0, .12));border-color:var(--fk-list-editor-drag-border-color, var(--fk-color-primary, #0a84ff))}\n"] }]
123
- }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }, { type: i0.Output, args: ["itemsChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], addLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "addLabel", required: false }] }], orderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "orderable", required: false }] }], showOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "showOrder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], lockedItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedItems", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], itemsChange: [{ type: i0.Output, args: ["itemsChange"] }], itemAdded: [{ type: i0.Output, args: ["itemAdded"] }], itemRemoved: [{ type: i0.Output, args: ["itemRemoved"] }], hostClass: [{
170
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n cdkDropList\n [cdkDropListDisabled]=\"disabled() || !orderable()\"\n (cdkDropListDropped)=\"onDrop($event)\"\n class=\"fk-list-editor__list\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n @for (item of items(); track $index) {\n <div\n cdkDrag\n [cdkDragDisabled]=\"disabled() || !orderable()\"\n class=\"fk-list-editor__item\"\n >\n @if (orderable()) {\n <span class=\"fk-list-editor__handle\" cdkDragHandle>\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <circle cx=\"9\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n </svg>\n </span>\n }\n\n @if (showOrder()) {\n <span class=\"fk-list-editor__order\">{{ $index + 1 }}</span>\n }\n\n @if (itemTemplate(); as def) {\n <div class=\"fk-list-editor__item-custom\">\n <ng-container\n [ngTemplateOutlet]=\"def.templateRef\"\n [ngTemplateOutletContext]=\"contextFor(item, $index)\"\n />\n </div>\n } @else {\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [value]=\"item\"\n [disabled]=\"disabled() || isLocked(item)\"\n (input)=\"updateItem($index, $any($event.target).value)\"\n />\n\n <button\n class=\"fk-list-editor__remove\"\n type=\"button\"\n [disabled]=\"disabled() || isLocked(item)\"\n [title]=\"\n isLocked(item) ? 'This item cannot be removed' : 'Remove item'\n \"\n (click)=\"removeItem($index)\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n\n <div *cdkDragPlaceholder class=\"fk-list-editor__placeholder\"></div>\n </div>\n }\n</div>\n\n@if (items().length === 0 && emptyMessage()) {\n <p class=\"fk-list-editor__empty\">{{ emptyMessage() }}</p>\n}\n\n<div class=\"fk-list-editor__add\">\n <div class=\"fk-list-editor__add-input\">\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [ngModel]=\"newItemValue()\"\n (ngModelChange)=\"newItemValue.set($event)\"\n (keydown)=\"onKeydown($event)\"\n />\n\n @if (newItemValue() && !disabled()) {\n <button\n class=\"fk-list-editor__add-clear\"\n type=\"button\"\n aria-label=\"Clear input\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"newItemValue.set('')\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n </div>\n\n <fk-button\n variant=\"outline\"\n size=\"sm\"\n [disabled]=\"!canAdd() || disabled()\"\n (click)=\"addItem()\"\n >\n <svg\n fkButtonIconStart\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 5v14M5 12h14\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n {{ addLabel() }}\n </fk-button>\n</div>\n\n@if (hasDuplicates()) {\n <p class=\"fk-list-editor__error\">Item names must be unique.</p>\n}\n", styles: [":host{display:block}.fk-list-editor__list{display:flex;flex-direction:column;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem))}.fk-list-editor__item{display:flex;align-items:center;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem));padding:var(--fk-list-editor-item-padding, var(--fk-rhythm-2, .5rem) var(--fk-rhythm-3, .75rem));background:var(--fk-list-editor-item-bg, var(--fk-color-surface, #ffffff));border:1px solid var(--fk-list-editor-item-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-item-radius, var(--fk-radius-md, .5rem));transition:box-shadow .15s ease}.fk-list-editor__item:hover{box-shadow:var(--fk-list-editor-item-hover-shadow, 0 1px 3px rgba(0, 0, 0, .06))}.fk-list-editor__icon{width:var(--fk-list-editor-icon-size, 1rem);height:var(--fk-list-editor-icon-size, 1rem)}.fk-list-editor__handle{display:flex;cursor:grab;color:var(--fk-list-editor-handle-color, var(--fk-color-muted, #8a98a8));padding:var(--fk-list-editor-handle-padding, var(--fk-rhythm-1, .25rem))}.fk-list-editor__handle:active{cursor:grabbing}.fk-list-editor__order{display:flex;align-items:center;justify-content:center;width:var(--fk-list-editor-order-size, 1.5rem);height:var(--fk-list-editor-order-size, 1.5rem);border-radius:var(--fk-list-editor-order-radius, 50%);background:var(--fk-list-editor-order-bg, var(--fk-color-primary-light, #e0f2fe));color:var(--fk-list-editor-order-color, var(--fk-color-primary, #0a84ff));font-size:var(--fk-list-editor-order-font-size, .75rem);font-weight:var(--fk-list-editor-order-font-weight, var(--fk-font-weight-semibold, 600));flex-shrink:0}.fk-list-editor__item-custom{flex:1;min-width:0}.fk-list-editor__input{flex:1;min-width:0;padding:var(--fk-list-editor-input-padding, var(--fk-rhythm-2, .5rem) var(--fk-rhythm-3, .75rem));border:1px solid var(--fk-list-editor-input-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-input-radius, var(--fk-radius-md, .5rem));font-size:var(--fk-list-editor-input-font-size, .875rem);color:var(--fk-list-editor-input-color, var(--fk-color-text, #1f2d3d));background:var(--fk-list-editor-input-bg, var(--fk-color-surface, #ffffff));outline:none;transition:border-color .15s ease}.fk-list-editor__input:focus{border-color:var(--fk-list-editor-input-focus-border-color, var(--fk-color-primary, #0a84ff))}.fk-list-editor__input:disabled{opacity:var(--fk-list-editor-disabled-opacity, var(--fk-input-disabled-opacity, .5));cursor:not-allowed}.fk-list-editor__remove{display:flex;align-items:center;padding:var(--fk-list-editor-remove-padding, var(--fk-rhythm-1, .25rem));border:none;background:transparent;cursor:pointer;color:var(--fk-list-editor-remove-color, var(--fk-color-muted, #8a98a8));transition:color .15s ease}.fk-list-editor__remove:hover:not(:disabled){color:var(--fk-list-editor-remove-hover-color, var(--fk-color-danger, #e02424))}.fk-list-editor__remove:focus-visible{outline:none;box-shadow:var(--fk-list-editor-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-list-editor__remove:disabled{opacity:.35;cursor:not-allowed}.fk-list-editor__placeholder{height:var(--fk-list-editor-placeholder-height, 3rem);border:2px dashed var(--fk-list-editor-placeholder-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-list-editor-item-radius, var(--fk-radius-md, .5rem));background:var(--fk-list-editor-placeholder-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-list-editor__empty{padding:var(--fk-list-editor-empty-padding, var(--fk-rhythm-4, 1rem));text-align:var(--fk-list-editor-empty-text-align, left);color:var(--fk-list-editor-empty-color, var(--fk-color-muted, #8a98a8));font-size:var(--fk-list-editor-empty-font-size, var(--fk-typography-small-font-size, .875rem))}.fk-list-editor__add{display:flex;align-items:center;gap:var(--fk-list-editor-row-gap, var(--fk-rhythm-2, .5rem));margin-top:var(--fk-list-editor-add-gap, var(--fk-rhythm-3, .75rem))}.fk-list-editor__add-input{position:relative;display:flex;flex:1;min-width:0}.fk-list-editor__add-input .fk-list-editor__input{padding-right:var(--fk-list-editor-add-clear-reserve, var(--fk-rhythm-7, 1.75rem))}.fk-list-editor__add-clear{position:absolute;top:50%;right:var(--fk-list-editor-add-clear-offset, var(--fk-rhythm-2, .5rem));transform:translateY(-50%);display:flex;align-items:center;justify-content:center;padding:var(--fk-list-editor-add-clear-padding, .125rem);border:0;background:transparent;border-radius:var(--fk-list-editor-add-clear-radius, var(--fk-radius-sm, .25rem));color:var(--fk-list-editor-remove-color, var(--fk-color-muted, #8a98a8));cursor:pointer;transition:color .15s ease}.fk-list-editor__add-clear:hover{color:var(--fk-list-editor-remove-hover-color, var(--fk-color-danger, #e02424))}.fk-list-editor__add-clear:focus-visible{outline:none;box-shadow:var(--fk-list-editor-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-list-editor__error{margin-top:var(--fk-list-editor-error-gap, var(--fk-rhythm-2, .5rem));color:var(--fk-list-editor-error-color, var(--fk-color-danger, #e02424));font-size:var(--fk-list-editor-error-font-size, var(--fk-typography-small-font-size, .875rem))}:host.fk-list-editor--sm .fk-list-editor__input{font-size:var(--fk-list-editor-input-font-size-sm, .8125rem);padding:var(--fk-list-editor-input-padding-sm, var(--fk-rhythm-1, .25rem) var(--fk-rhythm-2, .5rem))}:host.fk-list-editor--sm .fk-list-editor__item{padding:var(--fk-list-editor-item-padding-sm, var(--fk-rhythm-1, .25rem) var(--fk-rhythm-2, .5rem))}:host.fk-list-editor--disabled{opacity:var(--fk-list-editor-disabled-opacity, var(--fk-input-disabled-opacity, .5));pointer-events:none}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.fk-list-editor__list.cdk-drop-list-dragging .fk-list-editor__item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drag-preview.fk-list-editor__item{box-shadow:var(--fk-list-editor-drag-shadow, 0 4px 12px rgba(0, 0, 0, .12));border-color:var(--fk-list-editor-drag-border-color, var(--fk-color-primary, #0a84ff))}\n"] }]
171
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }, { type: i0.Output, args: ["itemsChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], addLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "addLabel", required: false }] }], orderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "orderable", required: false }] }], showOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "showOrder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], lockedItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedItems", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], itemsChange: [{ type: i0.Output, args: ["itemsChange"] }], itemAdded: [{ type: i0.Output, args: ["itemAdded"] }], itemRemoved: [{ type: i0.Output, args: ["itemRemoved"] }], itemTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FkListEditorItemDirective), { isSignal: true }] }], hostClass: [{
124
172
  type: HostBinding,
125
173
  args: ['class']
126
174
  }] } });
@@ -129,5 +177,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
129
177
  * Generated bundle index. Do not edit.
130
178
  */
131
179
 
132
- export { ListEditorComponent };
180
+ export { FkListEditorItemDirective, ListEditorComponent };
133
181
  //# sourceMappingURL=frame-kit-ui-ng-ui-list-editor.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"frame-kit-ui-ng-ui-list-editor.mjs","sources":["../../../../packages/ui-ng/ui/list-editor/list-editor.component.ts","../../../../packages/ui-ng/ui/list-editor/list-editor.component.html","../../../../packages/ui-ng/ui/list-editor/frame-kit-ui-ng-ui-list-editor.ts"],"sourcesContent":["import {\n CdkDrag,\n CdkDragDrop,\n CdkDragHandle,\n CdkDragPlaceholder,\n CdkDropList,\n moveItemInArray,\n} from '@angular/cdk/drag-drop';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { ButtonComponent } from '@frame-kit/ui-ng/ui/button';\nimport { FkButtonIconStartDirective } from '@frame-kit/ui-ng/ui/button';\nimport type { ListEditorSize } from './list-editor.types';\n\n@Component({\n selector: 'fk-list-editor',\n standalone: true,\n imports: [\n FormsModule,\n CdkDropList,\n CdkDrag,\n CdkDragHandle,\n CdkDragPlaceholder,\n ButtonComponent,\n FkButtonIconStartDirective,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './list-editor.component.html',\n styleUrl: './list-editor.component.scss',\n})\nexport class ListEditorComponent {\n // ===== INPUTS =====\n /** The current list of string items; two-way bindable via `[(items)]`. */\n readonly items = model<string[]>([]);\n /** Placeholder text for the new-item input field. */\n readonly placeholder = input('Add an item...');\n /** Message displayed when the list is empty. */\n readonly emptyMessage = input('No items added yet.');\n /** Label for the add button. */\n readonly addLabel = input('Add');\n /** When true, items can be reordered via drag-and-drop. */\n readonly orderable = input(true);\n /** When true, shows a numeric position indicator next to each item. */\n readonly showOrder = input(true);\n /** Size variant controlling the spacing and font of the component. */\n readonly size = input<ListEditorSize>('md');\n /** Items that cannot be removed by the user (matched case-insensitively). */\n readonly lockedItems = input<string[]>([]);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly disabled = input(false);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== OUTPUTS =====\n /** Fires whenever the items array changes, emitting the full updated array. */\n readonly itemsChange = output<string[]>();\n /** Fires when a new item is successfully added, emitting the added value. */\n readonly itemAdded = output<string>();\n /** Fires when an item is removed, emitting the removed value and its former index. */\n readonly itemRemoved = output<{ item: string; index: number }>();\n\n // ===== LOCAL STATE =====\n readonly newItemValue = signal('');\n\n // ===== COMPUTED =====\n readonly canAdd = computed(() => {\n const value = this.newItemValue().trim();\n\n if (!value) {\n return false;\n }\n\n const existing = this.items().map((n) => n.trim().toLowerCase());\n\n return !existing.includes(value.toLowerCase());\n });\n\n readonly hasDuplicates = computed(() => {\n const names = this.items().map((n) => n.trim().toLowerCase());\n\n return new Set(names).size !== names.length;\n });\n\n readonly hasEmptyItems = computed(() => {\n return this.items().some((n) => n.trim() === '');\n });\n\n readonly classes = computed(() =>\n [\n 'fk-list-editor',\n `fk-list-editor--${this.size()}`,\n this.disabled() ? 'fk-list-editor--disabled' : '',\n this.className(),\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n @HostBinding('class')\n get hostClass() {\n return this.classes();\n }\n\n // ===== ACTIONS =====\n\n /** Adds the current input value as a new item if it is non-empty and not a duplicate. */\n addItem(): void {\n const value = this.newItemValue().trim();\n\n if (!value || !this.canAdd() || this.disabled()) {\n return;\n }\n\n this.items.update((prev) => [...prev, value]);\n this.newItemValue.set('');\n this.itemsChange.emit(this.items());\n this.itemAdded.emit(value);\n }\n\n /** Removes the item at the given index unless it is locked or the component is disabled. */\n removeItem(index: number): void {\n const item = this.items()[index];\n\n if (this.disabled() || this.isLocked(item)) {\n return;\n }\n\n this.items.update((prev) => prev.filter((_, i) => i !== index));\n this.itemsChange.emit(this.items());\n this.itemRemoved.emit({ item, index });\n }\n\n /** Updates the item at the given index with a new value and emits the changed array. */\n updateItem(index: number, value: string): void {\n this.items.update((prev) =>\n prev.map((item, i) => (i === index ? value : item)),\n );\n this.itemsChange.emit(this.items());\n }\n\n onDrop(event: CdkDragDrop<string[]>): void {\n const updated = [...this.items()];\n\n moveItemInArray(updated, event.previousIndex, event.currentIndex);\n this.items.set(updated);\n this.itemsChange.emit(this.items());\n }\n\n /** Returns true if the given item matches any entry in `lockedItems`. */\n isLocked(item: string): boolean {\n return this.lockedItems().includes(item.trim().toLowerCase());\n }\n\n onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Enter') {\n event.preventDefault();\n this.addItem();\n }\n }\n}\n","<div\n cdkDropList\n [cdkDropListDisabled]=\"disabled() || !orderable()\"\n (cdkDropListDropped)=\"onDrop($event)\"\n class=\"fk-list-editor__list\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n @for (item of items(); track $index) {\n <div\n cdkDrag\n [cdkDragDisabled]=\"disabled() || !orderable()\"\n class=\"fk-list-editor__item\"\n >\n @if (orderable()) {\n <span class=\"fk-list-editor__handle\" cdkDragHandle>\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <circle cx=\"9\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n </svg>\n </span>\n }\n\n @if (showOrder()) {\n <span class=\"fk-list-editor__order\">{{ $index + 1 }}</span>\n }\n\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [value]=\"item\"\n [disabled]=\"disabled() || isLocked(item)\"\n (input)=\"updateItem($index, $any($event.target).value)\"\n />\n\n <button\n class=\"fk-list-editor__remove\"\n type=\"button\"\n [disabled]=\"disabled() || isLocked(item)\"\n [title]=\"isLocked(item) ? 'This item cannot be removed' : 'Remove item'\"\n (click)=\"removeItem($index)\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n\n <div *cdkDragPlaceholder class=\"fk-list-editor__placeholder\"></div>\n </div>\n }\n</div>\n\n@if (items().length === 0 && emptyMessage()) {\n <p class=\"fk-list-editor__empty\">{{ emptyMessage() }}</p>\n}\n\n<div class=\"fk-list-editor__add\">\n <div class=\"fk-list-editor__add-input\">\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [ngModel]=\"newItemValue()\"\n (ngModelChange)=\"newItemValue.set($event)\"\n (keydown)=\"onKeydown($event)\"\n />\n\n @if (newItemValue() && !disabled()) {\n <button\n class=\"fk-list-editor__add-clear\"\n type=\"button\"\n aria-label=\"Clear input\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"newItemValue.set('')\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n </div>\n\n <fk-button\n variant=\"outline\"\n size=\"sm\"\n [disabled]=\"!canAdd() || disabled()\"\n (click)=\"addItem()\"\n >\n <svg\n fkButtonIconStart\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 5v14M5 12h14\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n {{ addLabel() }}\n </fk-button>\n</div>\n\n@if (hasDuplicates()) {\n <p class=\"fk-list-editor__error\">Item names must be unique.</p>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"frame-kit-ui-ng-ui-list-editor.mjs","sources":["../../../../packages/ui-ng/ui/list-editor/list-editor-item.directive.ts","../../../../packages/ui-ng/ui/list-editor/list-editor.component.ts","../../../../packages/ui-ng/ui/list-editor/list-editor.component.html","../../../../packages/ui-ng/ui/list-editor/frame-kit-ui-ng-ui-list-editor.ts"],"sourcesContent":["import { Directive, inject, TemplateRef } from '@angular/core';\n\nimport { ListEditorItemContext } from './list-editor.types';\n\n/**\n * Marks an `<ng-template>` as the per-row renderer for `fk-list-editor`. When\n * present, the editor renders this template for each item instead of its\n * default editable input + remove button, so the consumer owns the row markup\n * (scheme tag, status, custom actions, …). The add-input, empty state, and\n * optional drag/order chrome are still provided by the editor.\n *\n * ```html\n * <fk-list-editor [items]=\"origins\">\n * <ng-template fkListEditorItem let-origin let-remove=\"remove\">\n * <span>{{ origin }}</span>\n * <button (click)=\"remove()\">Remove</button>\n * </ng-template>\n * </fk-list-editor>\n * ```\n */\n@Directive({\n selector: '[fkListEditorItem]',\n standalone: true,\n})\nexport class FkListEditorItemDirective {\n readonly templateRef = inject(TemplateRef<ListEditorItemContext>);\n}\n","import {\n CdkDrag,\n CdkDragDrop,\n CdkDragHandle,\n CdkDragPlaceholder,\n CdkDropList,\n moveItemInArray,\n} from '@angular/cdk/drag-drop';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n contentChild,\n HostBinding,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { ButtonComponent } from '@frame-kit/ui-ng/ui/button';\nimport { FkButtonIconStartDirective } from '@frame-kit/ui-ng/ui/button';\nimport { FkListEditorItemDirective } from './list-editor-item.directive';\nimport type {\n ListEditorItemContext,\n ListEditorSize,\n} from './list-editor.types';\n\n@Component({\n selector: 'fk-list-editor',\n standalone: true,\n imports: [\n FormsModule,\n NgTemplateOutlet,\n CdkDropList,\n CdkDrag,\n CdkDragHandle,\n CdkDragPlaceholder,\n ButtonComponent,\n FkButtonIconStartDirective,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './list-editor.component.html',\n styleUrl: './list-editor.component.scss',\n})\nexport class ListEditorComponent {\n // ===== INPUTS =====\n /** The current list of string items; two-way bindable via `[(items)]`. */\n readonly items = model<string[]>([]);\n /** Placeholder text for the new-item input field. */\n readonly placeholder = input('Add an item...');\n /** Message displayed when the list is empty. */\n readonly emptyMessage = input('No items added yet.');\n /** Label for the add button. */\n readonly addLabel = input('Add');\n /** When true, items can be reordered via drag-and-drop. */\n readonly orderable = input(true);\n /** When true, shows a numeric position indicator next to each item. */\n readonly showOrder = input(true);\n /** Size variant controlling the spacing and font of the component. */\n readonly size = input<ListEditorSize>('md');\n /** Items that cannot be removed by the user (matched case-insensitively). */\n readonly lockedItems = input<string[]>([]);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly disabled = input(false);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== OUTPUTS =====\n /** Fires whenever the items array changes, emitting the full updated array. */\n readonly itemsChange = output<string[]>();\n /** Fires when a new item is successfully added, emitting the added value. */\n readonly itemAdded = output<string>();\n /** Fires when an item is removed, emitting the removed value and its former index. */\n readonly itemRemoved = output<{ item: string; index: number }>();\n\n // ===== CONTENT =====\n /**\n * Optional custom row template (`*fkListEditorItem`). When provided, each\n * item renders through it instead of the default editable input + remove\n * button.\n */\n readonly itemTemplate = contentChild(FkListEditorItemDirective);\n\n // ===== LOCAL STATE =====\n readonly newItemValue = signal('');\n\n // ===== COMPUTED =====\n readonly canAdd = computed(() => {\n const value = this.newItemValue().trim();\n\n if (!value) {\n return false;\n }\n\n const existing = this.items().map((n) => n.trim().toLowerCase());\n\n return !existing.includes(value.toLowerCase());\n });\n\n readonly hasDuplicates = computed(() => {\n const names = this.items().map((n) => n.trim().toLowerCase());\n\n return new Set(names).size !== names.length;\n });\n\n readonly hasEmptyItems = computed(() => {\n return this.items().some((n) => n.trim() === '');\n });\n\n readonly classes = computed(() =>\n [\n 'fk-list-editor',\n `fk-list-editor--${this.size()}`,\n this.disabled() ? 'fk-list-editor--disabled' : '',\n this.className(),\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n @HostBinding('class')\n get hostClass() {\n return this.classes();\n }\n\n // ===== ACTIONS =====\n\n /** Adds the current input value as a new item if it is non-empty and not a duplicate. */\n addItem(): void {\n const value = this.newItemValue().trim();\n\n if (!value || !this.canAdd() || this.disabled()) {\n return;\n }\n\n this.items.update((prev) => [...prev, value]);\n this.newItemValue.set('');\n this.itemsChange.emit(this.items());\n this.itemAdded.emit(value);\n }\n\n /** Removes the item at the given index unless it is locked or the component is disabled. */\n removeItem(index: number): void {\n const item = this.items()[index];\n\n if (this.disabled() || this.isLocked(item)) {\n return;\n }\n\n this.items.update((prev) => prev.filter((_, i) => i !== index));\n this.itemsChange.emit(this.items());\n this.itemRemoved.emit({ item, index });\n }\n\n /** Updates the item at the given index with a new value and emits the changed array. */\n updateItem(index: number, value: string): void {\n this.items.update((prev) =>\n prev.map((item, i) => (i === index ? value : item)),\n );\n this.itemsChange.emit(this.items());\n }\n\n onDrop(event: CdkDragDrop<string[]>): void {\n const updated = [...this.items()];\n\n moveItemInArray(updated, event.previousIndex, event.currentIndex);\n this.items.set(updated);\n this.itemsChange.emit(this.items());\n }\n\n /** Returns true if the given item matches any entry in `lockedItems`. */\n isLocked(item: string): boolean {\n return this.lockedItems().includes(item.trim().toLowerCase());\n }\n\n /** Builds the `let-` context for a custom row template. */\n contextFor(item: string, index: number): ListEditorItemContext {\n return {\n $implicit: item,\n index,\n locked: this.isLocked(item),\n remove: () => this.removeItem(index),\n update: (value: string) => this.updateItem(index, value),\n };\n }\n\n onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Enter') {\n event.preventDefault();\n this.addItem();\n }\n }\n}\n","<div\n cdkDropList\n [cdkDropListDisabled]=\"disabled() || !orderable()\"\n (cdkDropListDropped)=\"onDrop($event)\"\n class=\"fk-list-editor__list\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n @for (item of items(); track $index) {\n <div\n cdkDrag\n [cdkDragDisabled]=\"disabled() || !orderable()\"\n class=\"fk-list-editor__item\"\n >\n @if (orderable()) {\n <span class=\"fk-list-editor__handle\" cdkDragHandle>\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <circle cx=\"9\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"6\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"9\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n <circle cx=\"15\" cy=\"18\" r=\"1.5\" fill=\"currentColor\" />\n </svg>\n </span>\n }\n\n @if (showOrder()) {\n <span class=\"fk-list-editor__order\">{{ $index + 1 }}</span>\n }\n\n @if (itemTemplate(); as def) {\n <div class=\"fk-list-editor__item-custom\">\n <ng-container\n [ngTemplateOutlet]=\"def.templateRef\"\n [ngTemplateOutletContext]=\"contextFor(item, $index)\"\n />\n </div>\n } @else {\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [value]=\"item\"\n [disabled]=\"disabled() || isLocked(item)\"\n (input)=\"updateItem($index, $any($event.target).value)\"\n />\n\n <button\n class=\"fk-list-editor__remove\"\n type=\"button\"\n [disabled]=\"disabled() || isLocked(item)\"\n [title]=\"\n isLocked(item) ? 'This item cannot be removed' : 'Remove item'\n \"\n (click)=\"removeItem($index)\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n\n <div *cdkDragPlaceholder class=\"fk-list-editor__placeholder\"></div>\n </div>\n }\n</div>\n\n@if (items().length === 0 && emptyMessage()) {\n <p class=\"fk-list-editor__empty\">{{ emptyMessage() }}</p>\n}\n\n<div class=\"fk-list-editor__add\">\n <div class=\"fk-list-editor__add-input\">\n <input\n class=\"fk-list-editor__input\"\n type=\"text\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [ngModel]=\"newItemValue()\"\n (ngModelChange)=\"newItemValue.set($event)\"\n (keydown)=\"onKeydown($event)\"\n />\n\n @if (newItemValue() && !disabled()) {\n <button\n class=\"fk-list-editor__add-clear\"\n type=\"button\"\n aria-label=\"Clear input\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"newItemValue.set('')\"\n >\n <svg\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n </div>\n\n <fk-button\n variant=\"outline\"\n size=\"sm\"\n [disabled]=\"!canAdd() || disabled()\"\n (click)=\"addItem()\"\n >\n <svg\n fkButtonIconStart\n class=\"fk-list-editor__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 5v14M5 12h14\"\n stroke=\"currentColor\"\n stroke-width=\"1.9\"\n stroke-linecap=\"round\"\n />\n </svg>\n {{ addLabel() }}\n </fk-button>\n</div>\n\n@if (hasDuplicates()) {\n <p class=\"fk-list-editor__error\">Item names must be unique.</p>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frame-kit/ui-ng",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Style-agnostic, token-driven Angular UI component library for FrameKit.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,7 +1,47 @@
1
1
  import * as _angular_core from '@angular/core';
2
+ import { TemplateRef } from '@angular/core';
2
3
  import { CdkDragDrop } from '@angular/cdk/drag-drop';
3
4
 
5
+ /**
6
+ * Marks an `<ng-template>` as the per-row renderer for `fk-list-editor`. When
7
+ * present, the editor renders this template for each item instead of its
8
+ * default editable input + remove button, so the consumer owns the row markup
9
+ * (scheme tag, status, custom actions, …). The add-input, empty state, and
10
+ * optional drag/order chrome are still provided by the editor.
11
+ *
12
+ * ```html
13
+ * <fk-list-editor [items]="origins">
14
+ * <ng-template fkListEditorItem let-origin let-remove="remove">
15
+ * <span>{{ origin }}</span>
16
+ * <button (click)="remove()">Remove</button>
17
+ * </ng-template>
18
+ * </fk-list-editor>
19
+ * ```
20
+ */
21
+ declare class FkListEditorItemDirective {
22
+ readonly templateRef: TemplateRef<any>;
23
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FkListEditorItemDirective, never>;
24
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FkListEditorItemDirective, "[fkListEditorItem]", never, {}, {}, never, never, true, never>;
25
+ }
26
+
4
27
  type ListEditorSize = 'sm' | 'md';
28
+ /**
29
+ * Context passed to a custom `fk-list-editor` row template (`*fkListEditorItem`)
30
+ * via the `let-` microsyntax. `$implicit` is the item value; `remove` / `update`
31
+ * act on this specific row.
32
+ */
33
+ interface ListEditorItemContext {
34
+ /** The item value. */
35
+ $implicit: string;
36
+ /** The item's position in the list. */
37
+ index: number;
38
+ /** True when the item is locked (non-removable). */
39
+ locked: boolean;
40
+ /** Remove this item from the list. */
41
+ remove: () => void;
42
+ /** Replace this item's value. */
43
+ update: (value: string) => void;
44
+ }
5
45
 
6
46
  declare class ListEditorComponent {
7
47
  /** The current list of string items; two-way bindable via `[(items)]`. */
@@ -32,6 +72,12 @@ declare class ListEditorComponent {
32
72
  item: string;
33
73
  index: number;
34
74
  }>;
75
+ /**
76
+ * Optional custom row template (`*fkListEditorItem`). When provided, each
77
+ * item renders through it instead of the default editable input + remove
78
+ * button.
79
+ */
80
+ readonly itemTemplate: _angular_core.Signal<FkListEditorItemDirective | undefined>;
35
81
  readonly newItemValue: _angular_core.WritableSignal<string>;
36
82
  readonly canAdd: _angular_core.Signal<boolean>;
37
83
  readonly hasDuplicates: _angular_core.Signal<boolean>;
@@ -47,10 +93,12 @@ declare class ListEditorComponent {
47
93
  onDrop(event: CdkDragDrop<string[]>): void;
48
94
  /** Returns true if the given item matches any entry in `lockedItems`. */
49
95
  isLocked(item: string): boolean;
96
+ /** Builds the `let-` context for a custom row template. */
97
+ contextFor(item: string, index: number): ListEditorItemContext;
50
98
  onKeydown(event: KeyboardEvent): void;
51
99
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ListEditorComponent, never>;
52
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<ListEditorComponent, "fk-list-editor", never, { "items": { "alias": "items"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "addLabel": { "alias": "addLabel"; "required": false; "isSignal": true; }; "orderable": { "alias": "orderable"; "required": false; "isSignal": true; }; "showOrder": { "alias": "showOrder"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "lockedItems": { "alias": "lockedItems"; "required": false; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, { "items": "itemsChange"; "itemsChange": "itemsChange"; "itemAdded": "itemAdded"; "itemRemoved": "itemRemoved"; }, never, never, true, never>;
100
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<ListEditorComponent, "fk-list-editor", never, { "items": { "alias": "items"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "addLabel": { "alias": "addLabel"; "required": false; "isSignal": true; }; "orderable": { "alias": "orderable"; "required": false; "isSignal": true; }; "showOrder": { "alias": "showOrder"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "lockedItems": { "alias": "lockedItems"; "required": false; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, { "items": "itemsChange"; "itemsChange": "itemsChange"; "itemAdded": "itemAdded"; "itemRemoved": "itemRemoved"; }, ["itemTemplate"], never, true, never>;
53
101
  }
54
102
 
55
- export { ListEditorComponent };
56
- export type { ListEditorSize };
103
+ export { FkListEditorItemDirective, ListEditorComponent };
104
+ export type { ListEditorItemContext, ListEditorSize };
@@ -30,13 +30,37 @@ A token-driven list editor for building ordered string lists with add, remove, i
30
30
 
31
31
  ### Content
32
32
 
33
- `fk-list-editor` does not use content projection. All configuration is via inputs.
33
+ By default the editor renders each item as an editable text input with a remove
34
+ button. To take over the row markup (status badges, custom actions, read-only
35
+ display, …), project an `<ng-template fkListEditorItem>`. When present, it
36
+ replaces the per-row input/remove for every item; the add-input, empty state,
37
+ and optional drag/order chrome are still provided by the editor.
38
+
39
+ The template receives a `ListEditorItemContext` via the `let-` microsyntax:
40
+
41
+ | Context | Type | Description |
42
+ | ----------- | ------------------------- | ------------------------------- |
43
+ | `$implicit` | `string` | The item value (`let-item`) |
44
+ | `index` | `number` | The item's position in the list |
45
+ | `locked` | `boolean` | Whether the item is locked |
46
+ | `remove` | `() => void` | Remove this item |
47
+ | `update` | `(value: string) => void` | Replace this item's value |
48
+
49
+ ```html
50
+ <fk-list-editor [items]="origins">
51
+ <ng-template fkListEditorItem let-origin let-remove="remove">
52
+ <span>{{ origin }}</span>
53
+ <button (click)="remove()">Remove</button>
54
+ </ng-template>
55
+ </fk-list-editor>
56
+ ```
34
57
 
35
58
  ## Features
36
59
 
37
60
  - Add items via text input with Enter key or button click
38
61
  - Remove items with the remove button
39
62
  - Inline edit existing items
63
+ - Custom per-row rendering via `*fkListEditorItem`
40
64
  - Drag-to-reorder with CDK drag-drop
41
65
  - Duplicate detection with error message
42
66
  - Lock specific items from editing or removal
@@ -53,13 +77,14 @@ A token-driven list editor for building ordered string lists with add, remove, i
53
77
  ## Import
54
78
 
55
79
  ```ts
56
- import { ListEditorComponent } from '@frame-kit/ui-ng';
80
+ import { ListEditorComponent, FkListEditorItemDirective } from '@frame-kit/ui-ng';
57
81
  ```
58
82
 
59
83
  ```ts
60
84
  @Component({
61
85
  selector: 'app-example',
62
- imports: [ListEditorComponent],
86
+ // FkListEditorItemDirective only needed when projecting a custom row template
87
+ imports: [ListEditorComponent, FkListEditorItemDirective],
63
88
  templateUrl: './example.component.html',
64
89
  })
65
90
  export class ExampleComponent {}
@@ -91,6 +116,21 @@ export class ExampleComponent {}
91
116
  <fk-list-editor [(items)]="levels" [lockedItems]="inUseLevels" placeholder="Add a level..." addLabel="Add Level" />
92
117
  ```
93
118
 
119
+ ### Custom row template
120
+
121
+ Render each row yourself (e.g. an allowed-origins editor with a scheme tag) via
122
+ `*fkListEditorItem`. The editor still supplies the add-input and empty state.
123
+
124
+ ```html
125
+ <fk-list-editor [(items)]="origins" [orderable]="false" [showOrder]="false" placeholder="https://app.example.com" addLabel="Add">
126
+ <ng-template fkListEditorItem let-origin let-remove="remove">
127
+ <span class="scheme">{{ origin.startsWith('https') ? 'https' : 'http' }}</span>
128
+ <span class="url">{{ origin }}</span>
129
+ <button type="button" (click)="remove()">Remove</button>
130
+ </ng-template>
131
+ </fk-list-editor>
132
+ ```
133
+
94
134
  ## Accessibility
95
135
 
96
136
  - The list container supports `ariaLabel` for screen readers