@ngstarter-ui/components 1.0.37 → 1.0.38

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.
@@ -6315,7 +6315,9 @@
6315
6315
  ],
6316
6316
  "inputs": [
6317
6317
  "absolute",
6318
- "autoHeight"
6318
+ "autoHeight",
6319
+ "block",
6320
+ "flex"
6319
6321
  ],
6320
6322
  "outputs": [],
6321
6323
  "cssTokens": [
@@ -7394,21 +7396,29 @@
7394
7396
  "useWhen": "Use ngs-sidebar as the content inside a shell sidebar region, usually inside Sidenav or LayoutSidebar, when an admin app or workspace needs persistent sidebar navigation and supporting sidebar content. Compose it with ngs-sidebar-header, ngs-sidebar-body, ngs-sidebar-footer, ngs-sidebar-nav, ngs-sidebar-nav-item, ngs-sidebar-nav-group, ngs-sidebar-nav-group-toggle, ngs-sidebar-nav-group-menu, ngs-sidebar-nav-heading, ngs-sidebar-divider, ngsSidebarNavItemIcon, ngsSidebarNavItemBadge, and ngsSidebarNavGroupToggleIcon. Good for brand or workspace header, main app navigation, grouped routes, badges, active item state through activeKey, autoScrollToActiveItem, and data-driven nav templates. Sidebar owns navigation; when ngs-sidebar is not used and persistent navigation is needed, use Navigation. Do not use Sidebar as a generic left column, card, drawer content, inspector, page section, tabs, menu, or compact icon rail. Use Sidenav for responsive open, collapse, or overlay shell behavior, RailNav for compact icon navigation, SidePanel for secondary tabbed tools, Drawer for temporary overlay side content, and Panel or Card for content grouping.",
7395
7397
  "exampleTopics": [
7396
7398
  "Basic sidebar",
7399
+ "Dynamic compact sidebar",
7400
+ "Only compact sidebar",
7397
7401
  "Sidebar with custom icons"
7398
7402
  ],
7399
- "minimalExample": "<div class=\"h-[600px] w-[300px] relative\">\n <ngs-sidebar class=\"border border-muted rounded-2xl\">\n <ngs-sidebar-header class=\"flex h-14 items-center px-4 font-bold text-primary border-b border-b-muted\">\n NGSTARTER\n </ngs-sidebar-header>\n <ngs-sidebar-body>\n <ngs-sidebar-nav activeKey=\"home\" class=\"p-4\">\n @for (navItem of navItems; track navItem) {\n @switch (navItem.type) {\n @case ('group') {\n <ngs-sidebar-nav-group>\n <ngs-sidebar-nav-group-toggle>\n <ng-container ngsSidebarNavItemIcon>\n @if (navItem.icon) {\n <ngs-icon name=\"fluent:{{ navItem.icon }}\"/>\n }\n </ng-container>\n {{ navItem.label }}\n <ngs-icon name=\"fluent:chevron-down-24-regular\" ngsSidebarNavGroupToggleIcon/>\n </ngs-sidebar-nav-group-toggle>\n <ngs-sidebar-nav-group-menu>\n @for (childNavItem of navItem.children; track childNavItem) {\n <ngs-sidebar-nav-item [key]=\"childNavItem.key\">\n <ng-container ngsSidebarNavItemIcon>\n @if (childNavItem.icon) {\n <ngs-icon name=\"fluent:{{ childNavItem.icon }}\"/>\n }\n </ng-container>\n {{ childNavItem.label }}\n </ngs-sidebar-nav-item>\n }\n </ngs-sidebar-nav-group-menu>\n </ngs-sidebar-nav-group>\n }\n @case (\n...",
7403
+ "minimalExample": "<div class=\"h-[600px] w-[300px] relative\">\n <ngs-sidebar class=\"border border-muted rounded-2xl\">\n <ngs-sidebar-header class=\"h-14 px-4 font-bold text-primary border-b border-b-muted\">\n NGSTARTER\n </ngs-sidebar-header>\n <ngs-sidebar-body>\n <ngs-sidebar-nav activeKey=\"home\" class=\"p-4\">\n @for (navItem of navItems; track navItem) {\n @switch (navItem.type) {\n @case ('group') {\n <ngs-sidebar-nav-group>\n <ngs-sidebar-nav-group-toggle>\n <ng-container ngsSidebarNavItemIcon>\n @if (navItem.icon) {\n <ngs-icon name=\"fluent:{{ navItem.icon }}\"/>\n }\n </ng-container>\n {{ navItem.label }}\n <ngs-icon name=\"fluent:chevron-down-24-regular\" class=\"size-4\" ngsSidebarNavGroupToggleIcon/>\n </ngs-sidebar-nav-group-toggle>\n <ngs-sidebar-nav-group-menu>\n @for (childNavItem of navItem.children; track childNavItem) {\n <ngs-sidebar-nav-item [key]=\"childNavItem.key\">\n <ng-container ngsSidebarNavItemIcon>\n @if (childNavItem.icon) {\n <ngs-icon name=\"fluent:{{ childNavItem.icon }}\"/>\n }\n </ng-container>\n {{ childNavItem.label }}\n </ngs-sidebar-nav-item>\n }\n </ngs-sidebar-nav-group-menu>\n </ngs-sidebar-nav-group>\n }\n @case ('he\n...",
7400
7404
  "exampleFiles": [
7401
7405
  {
7402
7406
  "name": "basic-sidebar-example",
7403
7407
  "title": "Basic sidebar",
7404
7408
  "file": "projects/docs/src/app/navigation/sidebar/_examples/basic-sidebar-example/basic-sidebar-example.html",
7405
- "source": "<div class=\"h-[600px] w-[300px] relative\">\n <ngs-sidebar class=\"border border-muted rounded-2xl\">\n <ngs-sidebar-header class=\"flex h-14 items-center px-4 font-bold text-primary border-b border-b-muted\">\n NGSTARTER\n </ngs-sidebar-header>\n <ngs-sidebar-body>\n <ngs-sidebar-nav activeKey=\"home\" class=\"p-4\">\n @for (navItem of navItems; track navItem) {\n @switch (navItem.type) {\n @case ('group') {\n <ngs-sidebar-nav-group>\n <ngs-sidebar-nav-group-toggle>\n <ng-container ngsSidebarNavItemIcon>\n @if (navItem.icon) {\n <ngs-icon name=\"fluent:{{ navItem.icon }}\"/>\n }\n </ng-container>\n {{ navItem.label }}\n <ngs-icon name=\"fluent:chevron-down-24-regular\" ngsSidebarNavGroupToggleIcon/>\n </ngs-sidebar-nav-group-toggle>\n <ngs-sidebar-nav-group-menu>\n @for (childNavItem of navItem.children; track childNavItem) {\n <ngs-sidebar-nav-item [key]=\"childNavItem.key\">\n <ng-container ngsSidebarNavItemIcon>\n @if (childNavItem.icon) {\n <ngs-icon name=\"fluent:{{ childNavItem.icon }}\"/>\n }\n </ng-container>\n {{ childNavItem.label }}\n </ngs-sidebar-nav-item>\n }\n </ngs-sidebar-nav-group-menu>\n </ngs-sidebar-nav-group>\n }\n @case (\n..."
7409
+ "source": "<div class=\"h-[600px] w-[300px] relative\">\n <ngs-sidebar class=\"border border-muted rounded-2xl\">\n <ngs-sidebar-header class=\"h-14 px-4 font-bold text-primary border-b border-b-muted\">\n NGSTARTER\n </ngs-sidebar-header>\n <ngs-sidebar-body>\n <ngs-sidebar-nav activeKey=\"home\" class=\"p-4\">\n @for (navItem of navItems; track navItem) {\n @switch (navItem.type) {\n @case ('group') {\n <ngs-sidebar-nav-group>\n <ngs-sidebar-nav-group-toggle>\n <ng-container ngsSidebarNavItemIcon>\n @if (navItem.icon) {\n <ngs-icon name=\"fluent:{{ navItem.icon }}\"/>\n }\n </ng-container>\n {{ navItem.label }}\n <ngs-icon name=\"fluent:chevron-down-24-regular\" class=\"size-4\" ngsSidebarNavGroupToggleIcon/>\n </ngs-sidebar-nav-group-toggle>\n <ngs-sidebar-nav-group-menu>\n @for (childNavItem of navItem.children; track childNavItem) {\n <ngs-sidebar-nav-item [key]=\"childNavItem.key\">\n <ng-container ngsSidebarNavItemIcon>\n @if (childNavItem.icon) {\n <ngs-icon name=\"fluent:{{ childNavItem.icon }}\"/>\n }\n </ng-container>\n {{ childNavItem.label }}\n </ngs-sidebar-nav-item>\n }\n </ngs-sidebar-nav-group-menu>\n </ngs-sidebar-nav-group>\n }\n @case ('he\n..."
7410
+ },
7411
+ {
7412
+ "name": "dynamic-compact-sidebar-example",
7413
+ "title": "Dynamic compact sidebar",
7414
+ "file": "projects/docs/src/app/navigation/sidebar/_examples/dynamic-compact-sidebar-example/dynamic-compact-sidebar-example.html",
7415
+ "source": "<div class=\"flex flex-col gap-4\">\n <button ngsButton=\"tonal\" type=\"button\" class=\"w-fit\" (click)=\"toggleCompact()\">\n <ngs-icon [name]=\"compact() ? 'fluent:panel-left-expand-24-regular' : 'fluent:panel-left-contract-24-regular'\"/>\n {{ compact() ? 'Expand sidebar' : 'Compact sidebar' }}\n </button>\n <div class=\"h-[420px] w-full relative border border-muted rounded-2xl overflow-hidden\">\n <ngs-sidenav-container autosize hasBackdrop=\"false\">\n <ngs-sidenav [opened]=\"true\" mode=\"side\" [collapsed]=\"compact()\" disableClose>\n <ngs-sidebar class=\"border-r border-r-muted\">\n <ngs-sidebar-header class=\"h-14 px-4 border-b border-b-muted text-primary font-bold\">\n <div *ngsSidenavCollapsed class=\"text-center w-full\">NG</div>\n <span *ngsSidenavExpanded>NgStarter</span>\n </ngs-sidebar-header>\n <ngs-sidebar-body>\n <ngs-sidebar-nav activeKey=\"home\" class=\"p-4\">\n @for (navItem of navItems; track navItem.key) {\n <ngs-sidebar-nav-item [key]=\"navItem.key\" [attr.aria-label]=\"navItem.label\">\n <ngs-icon [name]=\"navItem.icon\" ngsSidebarNavItemIcon/>\n {{ navItem.label }}\n </ngs-sidebar-nav-item>\n }\n </ngs-sidebar-nav>\n </ngs-sidebar-body>\n <ngs-sidebar-footer class=\"h-14 px-4 border-t border-t-muted text-sm\">\n <div *ngsSidenavCollapsed class=\"text-center w-full\">WS</div>\n <span *ngsSidenavExpanded>Workspace</span>\n </ngs-sidebar-footer>\n </ngs-sidebar>\n </ngs-sid\n..."
7406
7416
  },
7407
7417
  {
7408
- "name": "sidebar-with-custom-icons-example",
7409
- "title": "Sidebar with custom icons",
7410
- "file": "projects/docs/src/app/navigation/sidebar/_examples/sidebar-with-custom-icons-example/sidebar-with-custom-icons-example.html",
7411
- "source": "<div class=\"h-[600px] w-[300px] relative\">\n <ngs-sidebar class=\"border border-muted rounded-2xl\">\n <ngs-sidebar-header class=\"flex h-14 items-center px-4 font-bold border-b border-b-muted text-primary\">\n NgStarter\n </ngs-sidebar-header>\n <ngs-sidebar-body>\n <ngs-sidebar-nav activeKey=\"home\" class=\"p-4\">\n @for (navItem of navItems; track navItem) {\n @switch (navItem.type) {\n @case ('group') {\n <ngs-sidebar-nav-group>\n <ngs-sidebar-nav-group-toggle>\n <ng-container ngsSidebarNavItemIcon>\n @if (navItem.icon) {\n <ngs-icon [name]=\"navItem.icon\"/>\n }\n </ng-container>\n {{ navItem.label }}\n <ngs-icon ngsSidebarNavGroupToggleIcon name=\"fluent:add-24-regular\"/>\n </ngs-sidebar-nav-group-toggle>\n <ngs-sidebar-nav-group-menu>\n @for (childNavItem of navItem.children; track childNavItem) {\n <ngs-sidebar-nav-item [key]=\"childNavItem.key\">\n <ng-container ngsSidebarNavItemIcon>\n @if (childNavItem.icon) {\n <ngs-icon [name]=\"childNavItem.icon\"/>\n }\n </ng-container>\n {{ childNavItem.label }}\n </ngs-sidebar-nav-item>\n }\n </ngs-sidebar-nav-group-menu>\n </ngs-sidebar-nav-group>\n }\n @case ('heading') {\n <ngs\n..."
7418
+ "name": "only-compact-sidebar-example",
7419
+ "title": "Only compact sidebar",
7420
+ "file": "projects/docs/src/app/navigation/sidebar/_examples/only-compact-sidebar-example/only-compact-sidebar-example.html",
7421
+ "source": "<div class=\"h-[420px] w-24 relative\">\n <ngs-sidebar onlyCompact class=\"border border-muted rounded-2xl\">\n <ngs-sidebar-header class=\"h-14 px-4 border-b border-b-muted text-primary font-bold justify-center\">\n NG\n </ngs-sidebar-header>\n <ngs-sidebar-body>\n <ngs-sidebar-nav activeKey=\"dashboard\" class=\"p-3\">\n @for (navItem of navItems; track navItem.key) {\n <ngs-sidebar-nav-item [key]=\"navItem.key\" [attr.aria-label]=\"navItem.label\">\n <ngs-icon [name]=\"navItem.icon\" ngsSidebarNavItemIcon/>\n {{ navItem.label }}\n </ngs-sidebar-nav-item>\n }\n </ngs-sidebar-nav>\n </ngs-sidebar-body>\n <ngs-sidebar-footer class=\"h-14 px-4 border-t border-t-muted justify-center\">\n <ngs-icon name=\"fluent:person-circle-24-regular\"/>\n </ngs-sidebar-footer>\n </ngs-sidebar>\n</div>"
7412
7422
  }
7413
7423
  ],
7414
7424
  "previewAsset": "projects/components/sidebar/preview.svg",
@@ -7456,10 +7466,12 @@
7456
7466
  "inputs": [
7457
7467
  "activeKey",
7458
7468
  "autoScrollToActiveItem",
7469
+ "block",
7459
7470
  "dataSource",
7460
7471
  "forceActive",
7461
7472
  "itemTypeProperty",
7462
- "key"
7473
+ "key",
7474
+ "onlyCompact"
7463
7475
  ],
7464
7476
  "outputs": [
7465
7477
  "itemClicked"
@@ -3178,7 +3178,7 @@ class Effects {
3178
3178
  this.imageDesigner.effectsPortal.set(null);
3179
3179
  }
3180
3180
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: Effects, deps: [], target: i0.ɵɵFactoryTarget.Component });
3181
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: Effects, isStandalone: true, selector: "ngs-effects", ngImport: i0, template: "<ngs-panel class=\"h-full\">\n <ngs-panel-header class=\"border-b border-border flex items-center ps-4 pe-2\">\n <ngs-toolbar class=\"h-full\">\n <span class=\"font-medium\">Effects</span>\n <ngs-toolbar-spacer/>\n <button ngsButton=\"tonal\" (click)=\"resetAll()\" class=\"me-2 h-8 text-xs px-3\">\n Reset\n </button>\n <button ngsIconButton (click)=\"close()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </ngs-toolbar>\n </ngs-panel-header>\n <ngs-panel-content class=\"p-0 overflow-y-auto\">\n <div class=\"p-4\">\n <!-- Preset Effects -->\n <div class=\"flex gap-4 pb-6 scrollbar-hide flex-wrap\">\n @for (effect of effects() || []; track effect.id) {\n <button (click)=\"applyEffect(effect.id)\"\n class=\"flex flex-col items-center gap-2 group min-w-[80px]\">\n <div class=\"w-20 h-20 rounded-xl overflow-hidden border-2 transition-all group-hover:border-primary\"\n [class.border-primary]=\"(selectedLayer()?.['effect'] || 'none') === effect.id\"\n [class.border-transparent]=\"(selectedLayer()?.['effect'] || 'none') !== effect.id\">\n <img [src]=\"effect.thumb\" class=\"w-full h-full object-cover\" [alt]=\"effect.name\">\n </div>\n <span class=\"text-xs font-medium text-muted-foreground group-hover:text-foreground transition-colors\">\n {{ effect.name }}\n </span>\n </button>\n }\n </div>\n\n <!-- Adjustments -->\n <div class=\"flex flex-col gap-6\">\n @for (adj of adjustments() || []; track adj.id) {\n <div class=\"flex flex-col gap-3\">\n <div class=\"flex items-center justify-between\">\n <span class=\"text-sm font-medium\">{{ adj.name }}</span>\n <ngs-slide-toggle [checked]=\"selectedLayer()?.[adj.id + 'Enabled']\"\n (change)=\"toggleAdjustment(adj.id, $event.checked)\"/>\n </div>\n\n @if (selectedLayer()?.[adj.id + 'Enabled']) {\n <div class=\"flex flex-col gap-4\">\n @if (adj.isGroup) {\n @for (control of adj.controls || []; track control.id) {\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex items-center justify-between\">\n <span class=\"text-xs text-muted-foreground\">{{ control.name }}</span>\n @if (control.type === 'color') {\n <button ngs-color-picker-thumbnail\n [color]=\"selectedLayer()?.[control.id] || control.default\"\n [ngsColorPickerTriggerFor]=\"colorPicker\"\n class=\"cursor-pointer border border-border rounded\"></button>\n <ng-template #colorPicker>\n <ngs-color-picker [ngModel]=\"selectedLayer()?.[control.id] || control.default\"\n (ngModelChange)=\"updateAdjustment(control.id, $event)\"/>\n </ng-template>\n } @else {\n <input ngsInput\n type=\"number\"\n class=\"w-16 h-8 text-center text-sm border border-border rounded-md bg-surface-container\"\n [ngModel]=\"selectedLayer()?.[control.id] ?? control.default\"\n (ngModelChange)=\"updateAdjustment(control.id, $event)\">\n }\n </div>\n @if (control.type !== 'color') {\n <ngs-slider [min]=\"control.min\"\n [max]=\"control.max\"\n [step]=\"control.step\">\n <input ngsSliderThumb\n [value]=\"selectedLayer()?.[control.id] ?? control.default\"\n (valueChange)=\"updateAdjustment(control.id, $event)\">\n </ngs-slider>\n }\n </div>\n }\n } @else {\n <div class=\"flex items-center gap-4\">\n @if (adj.id === 'border') {\n <button ngs-color-picker-thumbnail\n [color]=\"selectedLayer()?.['borderColor'] || selectedLayer()?.['fill'] || '#000000'\"\n [ngsColorPickerTriggerFor]=\"borderPicker\"\n class=\"cursor-pointer border border-border rounded\"></button>\n <ng-template #borderPicker>\n <ngs-color-picker [ngModel]=\"selectedLayer()?.['borderColor'] || selectedLayer()?.['fill'] || '#000000'\"\n (ngModelChange)=\"updateAdjustment('borderColor', $event)\"/>\n </ng-template>\n }\n <ngs-slider class=\"flex-1\"\n [min]=\"adj.min\"\n [max]=\"adj.max\"\n [step]=\"adj.step\">\n <input ngsSliderThumb\n [value]=\"selectedLayer()?.[adj.id] ?? adj.default\"\n (valueChange)=\"updateAdjustment(adj.id, $event)\">\n </ngs-slider>\n <input ngsInput\n type=\"number\"\n class=\"w-16 h-8 text-center text-sm border border-border rounded-md bg-surface-container\"\n [ngModel]=\"selectedLayer()?.[adj.id] ?? adj.default\"\n (ngModelChange)=\"updateAdjustment(adj.id, $event)\">\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;height:100%}:host .scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}:host .scrollbar-hide::-webkit-scrollbar{display:none}:host ngs-slider{--ngs-slider-track-height: 4px;--ngs-slider-thumb-size: 16px}:host input[type=number]::-webkit-inner-spin-button,:host input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.ngs-color-picker-thumbnail{width:32px;height:32px;min-width:32px;background:var(--ngs-color-picker-thumbnail-bg, #000)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }, { kind: "component", type: Slider, selector: "ngs-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "max", "step", "displayWith"], exportAs: ["ngsSlider"] }, { kind: "directive", type: SliderThumb, selector: "input[ngsSliderThumb]", inputs: ["value"], outputs: ["valueChange"], exportAs: ["ngsSliderThumb"] }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Toolbar, selector: "ngs-toolbar", exportAs: ["ngsToolbar"] }, { kind: "component", type: ToolbarSpacer, selector: "ngs-toolbar-spacer" }, { kind: "component", type: ColorPicker, selector: "ngs-color-picker", inputs: ["color", "disabled", "asDropdown", "showOpacity", "resultFormat"], outputs: ["colorChange", "rawColorChange"], exportAs: ["ngsColorPicker"] }, { kind: "component", type: ColorPickerThumbnail, selector: "ngs-color-picker-thumbnail,[ngs-color-picker-thumbnail]", inputs: ["color"], exportAs: ["ngsColorPickerThumbnail"] }, { kind: "directive", type: ColorPickerTriggerForDirective, selector: "[ngsColorPickerTriggerFor]", inputs: ["ngsColorPickerTriggerFor", "position"], outputs: ["opened", "closed"], exportAs: ["ngsColorPickerTriggerFor"] }] });
3181
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: Effects, isStandalone: true, selector: "ngs-effects", ngImport: i0, template: "<ngs-panel class=\"h-full\">\n <ngs-panel-header class=\"border-b border-border flex items-center ps-4 pe-2\">\n <ngs-toolbar class=\"h-full\">\n <span class=\"font-medium\">Effects</span>\n <ngs-toolbar-spacer/>\n <button ngsButton=\"tonal\" (click)=\"resetAll()\" class=\"me-2 h-8 text-xs px-3\">\n Reset\n </button>\n <button ngsIconButton (click)=\"close()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </ngs-toolbar>\n </ngs-panel-header>\n <ngs-panel-content class=\"p-0 overflow-y-auto\">\n <div class=\"p-4\">\n <!-- Preset Effects -->\n <div class=\"flex gap-4 pb-6 scrollbar-hide flex-wrap\">\n @for (effect of effects() || []; track effect.id) {\n <button (click)=\"applyEffect(effect.id)\"\n class=\"flex flex-col items-center gap-2 group min-w-[80px]\">\n <div class=\"w-20 h-20 rounded-xl overflow-hidden border-2 transition-all group-hover:border-primary\"\n [class.border-primary]=\"(selectedLayer()?.['effect'] || 'none') === effect.id\"\n [class.border-transparent]=\"(selectedLayer()?.['effect'] || 'none') !== effect.id\">\n <img [src]=\"effect.thumb\" class=\"w-full h-full object-cover\" [alt]=\"effect.name\">\n </div>\n <span class=\"text-xs font-medium text-muted-foreground group-hover:text-foreground transition-colors\">\n {{ effect.name }}\n </span>\n </button>\n }\n </div>\n\n <!-- Adjustments -->\n <div class=\"flex flex-col gap-6\">\n @for (adj of adjustments() || []; track adj.id) {\n <div class=\"flex flex-col gap-3\">\n <div class=\"flex items-center justify-between\">\n <span class=\"text-sm font-medium\">{{ adj.name }}</span>\n <ngs-slide-toggle [checked]=\"selectedLayer()?.[adj.id + 'Enabled']\"\n (change)=\"toggleAdjustment(adj.id, $event.checked)\"/>\n </div>\n\n @if (selectedLayer()?.[adj.id + 'Enabled']) {\n <div class=\"flex flex-col gap-4\">\n @if (adj.isGroup) {\n @for (control of adj.controls || []; track control.id) {\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex items-center justify-between\">\n <span class=\"text-xs text-muted-foreground\">{{ control.name }}</span>\n @if (control.type === 'color') {\n <button ngs-color-picker-thumbnail\n [color]=\"selectedLayer()?.[control.id] || control.default\"\n [ngsColorPickerTriggerFor]=\"colorPicker\"\n class=\"cursor-pointer border border-border rounded\"></button>\n <ng-template #colorPicker>\n <ngs-color-picker [ngModel]=\"selectedLayer()?.[control.id] || control.default\"\n (ngModelChange)=\"updateAdjustment(control.id, $event)\"/>\n </ng-template>\n } @else {\n <input ngsInput\n type=\"number\"\n class=\"w-16 h-8 text-center text-sm border border-border rounded-md bg-surface-container\"\n [ngModel]=\"selectedLayer()?.[control.id] ?? control.default\"\n (ngModelChange)=\"updateAdjustment(control.id, $event)\">\n }\n </div>\n @if (control.type !== 'color') {\n <ngs-slider [min]=\"control.min\"\n [max]=\"control.max\"\n [step]=\"control.step\">\n <input ngsSliderThumb\n [value]=\"selectedLayer()?.[control.id] ?? control.default\"\n (valueChange)=\"updateAdjustment(control.id, $event)\">\n </ngs-slider>\n }\n </div>\n }\n } @else {\n <div class=\"flex items-center gap-4\">\n @if (adj.id === 'border') {\n <button ngs-color-picker-thumbnail\n [color]=\"selectedLayer()?.['borderColor'] || selectedLayer()?.['fill'] || '#000000'\"\n [ngsColorPickerTriggerFor]=\"borderPicker\"\n class=\"cursor-pointer border border-border rounded\"></button>\n <ng-template #borderPicker>\n <ngs-color-picker [ngModel]=\"selectedLayer()?.['borderColor'] || selectedLayer()?.['fill'] || '#000000'\"\n (ngModelChange)=\"updateAdjustment('borderColor', $event)\"/>\n </ng-template>\n }\n <ngs-slider class=\"flex-1\"\n [min]=\"adj.min\"\n [max]=\"adj.max\"\n [step]=\"adj.step\">\n <input ngsSliderThumb\n [value]=\"selectedLayer()?.[adj.id] ?? adj.default\"\n (valueChange)=\"updateAdjustment(adj.id, $event)\">\n </ngs-slider>\n <input ngsInput\n type=\"number\"\n class=\"w-16 h-8 text-center text-sm border border-border rounded-md bg-surface-container\"\n [ngModel]=\"selectedLayer()?.[adj.id] ?? adj.default\"\n (ngModelChange)=\"updateAdjustment(adj.id, $event)\">\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;height:100%}:host .scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}:host .scrollbar-hide::-webkit-scrollbar{display:none}:host ngs-slider{--ngs-slider-track-height: 4px;--ngs-slider-thumb-size: 16px}:host input[type=number]::-webkit-inner-spin-button,:host input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.ngs-color-picker-thumbnail{width:32px;height:32px;min-width:32px;background:var(--ngs-color-picker-thumbnail-bg, #000)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }, { kind: "component", type: Slider, selector: "ngs-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "max", "step", "displayWith"], exportAs: ["ngsSlider"] }, { kind: "directive", type: SliderThumb, selector: "input[ngsSliderThumb]", inputs: ["value"], outputs: ["valueChange"], exportAs: ["ngsSliderThumb"] }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Toolbar, selector: "ngs-toolbar", exportAs: ["ngsToolbar"] }, { kind: "component", type: ToolbarSpacer, selector: "ngs-toolbar-spacer" }, { kind: "component", type: ColorPicker, selector: "ngs-color-picker", inputs: ["color", "disabled", "asDropdown", "showOpacity", "resultFormat"], outputs: ["colorChange", "rawColorChange"], exportAs: ["ngsColorPicker"] }, { kind: "component", type: ColorPickerThumbnail, selector: "ngs-color-picker-thumbnail,[ngs-color-picker-thumbnail]", inputs: ["color"], exportAs: ["ngsColorPickerThumbnail"] }, { kind: "directive", type: ColorPickerTriggerForDirective, selector: "[ngsColorPickerTriggerFor]", inputs: ["ngsColorPickerTriggerFor", "position"], outputs: ["opened", "closed"], exportAs: ["ngsColorPickerTriggerFor"] }] });
3182
3182
  }
3183
3183
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: Effects, decorators: [{
3184
3184
  type: Component,
@@ -3950,7 +3950,7 @@ class ImageDesigner {
3950
3950
  provide: IMAGE_DESIGNER,
3951
3951
  useExisting: forwardRef(() => ImageDesigner)
3952
3952
  }
3953
- ], viewQueries: [{ propertyName: "canvasContainer", first: true, predicate: ["canvasContainer"], descendants: true, isSignal: true }], exportAs: ["ngsImageDesigner"], ngImport: i0, template: "<ngs-panel class=\"h-full\">\n <ngs-panel-sidebar class=\"border-r border-border h-full\">\n <ngs-tab-panel hideContentIfTabNotSelected [activeItemId]=\"activeItemId()\" class=\"h-full\">\n <ngs-tab-panel-content>\n <ngs-tab-panel-nav>\n <ngs-tab-panel-item for=\"text\" (click)=\"activeItemId.set('text')\">\n <ngs-icon name=\"fluent:text-field-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Text</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"photos\" (click)=\"activeItemId.set('photos')\">\n <ngs-icon name=\"fluent:image-copy-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Photos</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"elements\" (click)=\"activeItemId.set('elements')\">\n <ngs-icon name=\"fluent:shape-subtract-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Elements</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"upload\" (click)=\"activeItemId.set('upload')\">\n <ngs-icon name=\"fluent:arrow-upload-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Upload</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"background\" (click)=\"activeItemId.set('background')\">\n <ngs-icon name=\"fluent:grid-dots-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Background</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"layers\" (click)=\"activeItemId.set('layers')\">\n <ngs-icon name=\"fluent:layer-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Layers</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"resize\" (click)=\"activeItemId.set('resize')\">\n <ngs-icon name=\"fluent:resize-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Resize</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n </ngs-tab-panel-nav>\n </ngs-tab-panel-content>\n <ngs-tab-panel-aside class=\"border-s border-border\">\n <ng-template ngsTabPanelAsideContent=\"text\">\n <ngs-panel>\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">Text</ngs-panel-header>\n <ngs-panel-content class=\"p-4 flex flex-col gap-4\">\n <div class=\"grid grid-cols-1 gap-4 mt-2\">\n <button (click)=\"addHeader()\"\n draggable=\"true\"\n (dragstart)=\"onTextDragStart($event, 'header')\"\n class=\"w-full text-left p-4 border border-border rounded-lg hover:bg-surface-container transition-colors\">\n <div class=\"text-3xl font-bold pointer-events-none\">Add a heading</div>\n </button>\n <button (click)=\"addSubheader()\"\n draggable=\"true\"\n (dragstart)=\"onTextDragStart($event, 'subheader')\"\n class=\"w-full text-left p-3 border border-border rounded-lg hover:bg-surface-container transition-colors\">\n <div class=\"text-xl font-semibold pointer-events-none\">Add a subheading</div>\n </button>\n <button (click)=\"addBodyText()\"\n draggable=\"true\"\n (dragstart)=\"onTextDragStart($event, 'body')\"\n class=\"w-full text-left p-2 border border-border rounded-lg hover:bg-surface-container transition-colors\">\n <div class=\"text-base pointer-events-none\">Add body text</div>\n </button>\n </div>\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"photos\">\n <ngs-panel class=\"h-full flex flex-col\">\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">\n<!-- <ngs-form-field class=\"w-full\" subscriptHiddenIfEmpty>-->\n<!-- <ngs-icon name=\"fluent:search-24-regular\" ngsIconPrefix/>-->\n<!-- <input type=\"text\"-->\n<!-- ngsInput-->\n<!-- placeholder=\"Search photos\"-->\n<!-- [ngModel]=\"photosFilter()\"-->\n<!-- (ngModelChange)=\"photosFilter.set($event)\"/>-->\n<!-- </ngs-form-field>-->\n Photos\n </ngs-panel-header>\n <ngs-panel-content class=\"relative flex-1 p-0 overflow-hidden\">\n <ngs-scrollbar-area (scrolled)=\"onPhotosScroll($event)\" class=\"h-full\">\n <div class=\"p-4\">\n <div class=\"masonry-grid\">\n @for (photo of photos(); track photo.id) {\n <button (click)=\"addImage(photo)\"\n draggable=\"true\"\n (dragstart)=\"onImageDragStart($event, photo)\"\n [style.aspect-ratio]=\"photo.width + '/' + photo.height\"\n class=\"border border-border rounded-lg overflow-hidden hover:ring-2 hover:ring-primary transition-all bg-surface-container\">\n <img [src]=\"photo.thumbUrl || photo.url\"\n [alt]=\"photo.name\"\n class=\"w-full h-auto block pointer-events-none\"/>\n </button>\n }\n </div>\n @if (isLoadingPhotos()) {\n <div class=\"flex justify-center p-4\">\n <ngs-progress-spinner diameter=\"32\"/>\n </div>\n }\n @if (photos().length === 0 && !isLoadingPhotos()) {\n <div class=\"text-center p-4 text-sm text-neutral-500\">\n No photos found\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"elements\">\n <ngs-panel>\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">Elements</ngs-panel-header>\n <ngs-panel-content class=\"p-4 overflow-y-auto\">\n <div class=\"grid grid-cols-3 gap-3\">\n @for (element of elements(); track element.name) {\n <button (click)=\"addShape(element)\"\n draggable=\"true\"\n (dragstart)=\"onImageDragStart($event, element)\"\n class=\"aspect-square p-2 border border-border rounded-lg hover:bg-surface-container\n transition-colors flex items-center justify-center\">\n <img [src]=\"element.data\" [alt]=\"element.name\"\n class=\"max-w-full max-h-full object-contain pointer-events-none\"/>\n </button>\n }\n </div>\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"upload\">\n <ngs-panel class=\"h-full flex flex-col\">\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">\n <ngs-toolbar>\n <div>Upload</div>\n <ngs-toolbar-spacer/>\n <button ngsButton=\"filled\"\n ngsUploadTrigger\n [accept]=\"'image/*'\"\n (fileSelected)=\"onFileSelected($event)\"\n [loading]=\"isUploading()\">\n <ngs-icon name=\"fluent:arrow-upload-24-regular\"/>\n Add Image\n </button>\n </ngs-toolbar>\n </ngs-panel-header>\n <ngs-panel-content>\n @if (uploadedImages().length > 0 || assets().length > 0) {\n <ngs-scrollbar-area (scrolled)=\"onAssetsScroll($event)\">\n <div class=\"p-4 flex flex-col gap-4 min-h-full\">\n @if (isUploading() || uploadPreview()) {\n <div class=\"relative aspect-video border border-border rounded-lg overflow-hidden bg-surface-container\">\n @if (uploadPreview()) {\n <img [src]=\"uploadPreview()\" class=\"w-full h-full object-contain\" alt=\"Preview\"/>\n }\n @if (isUploading()) {\n <div class=\"absolute inset-0 bg-black/20 flex items-center justify-center backdrop-blur-[2px]\">\n <div class=\"flex flex-col items-center gap-2\">\n <ngs-progress-spinner diameter=\"32\" color=\"white\"/>\n <span class=\"text-xs font-medium text-white\">Uploading...</span>\n </div>\n </div>\n }\n </div>\n }\n\n @if (uploadedImages().length > 0) {\n <div class=\"flex flex-col gap-2\">\n <div class=\"text-sm font-semibold\">Your Uploads</div>\n <div class=\"masonry-grid\">\n @for (photo of uploadedImages(); track $index) {\n <button (click)=\"addUploadedImage(photo)\"\n draggable=\"true\"\n (dragstart)=\"onImageDragStart($event, photo)\"\n [style.aspect-ratio]=\"photo.width + '/' + photo.height\"\n class=\"border border-border rounded-lg overflow-hidden hover:ring-2\n hover:ring-primary transition-all bg-surface-container\">\n <img [src]=\"photo.url\" class=\"w-full h-auto block pointer-events-none\" alt=\"Uploaded image\"/>\n </button>\n }\n </div>\n </div>\n }\n\n @if (assets().length > 0) {\n <div class=\"flex flex-col gap-2\">\n <div class=\"text-sm font-semibold\">Assets</div>\n <div class=\"masonry-grid\">\n @for (asset of assets(); track asset.id) {\n <button (click)=\"addImage(asset)\"\n draggable=\"true\"\n (dragstart)=\"onImageDragStart($event, asset)\"\n [style.aspect-ratio]=\"asset.width + '/' + asset.height\"\n class=\"border border-border rounded-lg overflow-hidden hover:ring-2\n hover:ring-primary transition-all bg-surface-container\">\n <img [src]=\"asset.thumbUrl || asset.url\"\n [alt]=\"asset.name\"\n class=\"w-full h-auto block pointer-events-none\"/>\n </button>\n }\n </div>\n @if (isLoadingAssets()) {\n <div class=\"flex justify-center p-4\">\n <ngs-progress-spinner diameter=\"32\"/>\n </div>\n }\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n }\n\n @if (uploadedImages().length === 0 && assets().length === 0 && !isLoadingAssets()) {\n <div class=\"h-full flex items-center justify-center\">\n <div class=\"text-sm text-neutral-500\">No images uploaded yet.</div>\n </div>\n }\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"background\">\n <ngs-panel>\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">Background</ngs-panel-header>\n <ngs-panel-content>\n <ngs-tab-group animationDuration=\"0\" class=\"h-full\">\n <ngs-tab label=\"Color\">\n <div class=\"absolute inset-0\">\n <ngs-scrollbar-area>\n <div class=\"p-4\">\n <ngs-accordion [multi]=\"false\">\n <ngs-expansion-panel [expanded]=\"true\">\n <ngs-expansion-panel-header>\n <ngs-expansion-panel-title>Colors</ngs-expansion-panel-title>\n </ngs-expansion-panel-header>\n <div class=\"py-2\">\n <ngs-color-switcher [colors]=\"presetColors()\" (colorChange)=\"setColor($event)\"/>\n </div>\n </ngs-expansion-panel>\n <ngs-expansion-panel>\n <ngs-expansion-panel-header>\n <ngs-expansion-panel-title>Gradients</ngs-expansion-panel-title>\n </ngs-expansion-panel-header>\n <div class=\"grid grid-cols-3 gap-3 py-2\">\n @for (gradient of presetGradients(); track $index) {\n <button (click)=\"setGradient(gradient)\"\n [style.background]=\"gradient.css\"\n class=\"aspect-[16/6.75] w-full rounded-lg border border-border hover:ring-2 hover:ring-primary transition-all\">\n </button>\n }\n </div>\n </ngs-expansion-panel>\n </ngs-accordion>\n </div>\n </ngs-scrollbar-area>\n </div>\n </ngs-tab>\n <ngs-tab label=\"Patterns\">\n <div class=\"absolute inset-0\">\n <ngs-scrollbar-area>\n <div class=\"p-4\">\n <div class=\"grid grid-cols-3 gap-3\">\n @for (pattern of presetPatterns(); track $index) {\n <button (click)=\"addPattern(pattern)\"\n class=\"aspect-square w-full rounded-lg border border-border hover:ring-2 hover:ring-primary transition-all overflow-hidden\">\n <img [src]=\"pattern\" class=\"w-full h-full object-cover\" alt=\"pattern\">\n </button>\n }\n </div>\n </div>\n </ngs-scrollbar-area>\n </div>\n </ngs-tab>\n </ngs-tab-group>\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"layers\">\n <ngs-panel>\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">Layers</ngs-panel-header>\n <ngs-panel-content>\n @if (layersFromService().length > 0) {\n <ngs-list cdkDropList (cdkDropListDropped)=\"drop($event)\">\n @for (layer of layersFromService(); track layer.id) {\n <ngs-list-item cdkDrag\n cdkDragLockAxis=\"y\"\n [cdkDragStartDelay]=\"100\"\n (click)=\"selectLayer(layer.id!)\"\n [class.is-active]=\"selectedLayerId() === layer.id\"\n class=\"relative group bg-surface\">\n <ngs-icon [name]=\"getLayerIcon(layer.type)\" ngsListItemIcon/>\n <div ngsListItemLine>\n {{ layer.name || layer.type }}\n <!-- {{ layer.text || '' }}-->\n </div>\n <div class=\"absolute right-2 top-1/2 -translate-y-1/2 flex items-center\">\n <button ngsIconButton (click)=\"toggleLayerVisibility(layer.id!, $event)\">\n <ngs-icon\n [name]=\"layer.visible === false ? 'fluent:eye-off-24-regular' : 'fluent:eye-24-regular'\"/>\n </button>\n <button ngsIconButton (click)=\"toggleLayerLock(layer.id!, $event)\">\n <ngs-icon\n [name]=\"layer.locked ? 'fluent:lock-closed-24-regular' : 'fluent:lock-open-24-regular'\"\n class=\"size-5\"/>\n </button>\n <button ngsIconButton (click)=\"deleteLayer(layer.id!, $event)\" [disabled]=\"layer.locked\">\n <ngs-icon name=\"fluent:delete-24-regular\" class=\"size-5\"/>\n </button>\n </div>\n\n <div *cdkDragPlaceholder\n class=\"min-h-[var(--ngs-list-item-min-height)] bg-surface-container\"></div>\n </ngs-list-item>\n }\n </ngs-list>\n } @else {\n <div class=\"text-sm p-4 h-full flex items-center justify-center\">No layers available</div>\n }\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"resize\">\n <ngs-panel>\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">Resize</ngs-panel-header>\n <ngs-panel-content>\n <ngs-scrollbar-area>\n <div class=\"p-4 flex flex-col gap-4\">\n <div class=\"flex flex-col gap-4\">\n <ngs-form-field subscriptHiddenIfEmpty>\n <ngs-label>Width (px)</ngs-label>\n <input ngsInput type=\"number\" [(ngModel)]=\"resizeWidth\">\n </ngs-form-field>\n\n <ngs-form-field subscriptHiddenIfEmpty>\n <ngs-label>Height (px)</ngs-label>\n <input ngsInput type=\"number\" [(ngModel)]=\"resizeHeight\">\n </ngs-form-field>\n\n <ngs-form-field subscriptHiddenIfEmpty>\n <ngs-label>Units</ngs-label>\n <ngs-select [(ngModel)]=\"resizeUnit\">\n <ngs-option value=\"px\">px</ngs-option>\n </ngs-select>\n </ngs-form-field>\n\n <button ngsButton=\"filled\" fullWidth (click)=\"applyResize()\">Resize</button>\n </div>\n\n <div class=\"flex flex-col gap-6 mt-2\">\n @for (category of presetCategories(); track category.name) {\n <div class=\"flex flex-col gap-3\">\n <div class=\"flex items-center gap-2 font-semibold text-sm\">\n <ngs-icon [name]=\"category.icon\" class=\"size-5\"/>\n {{ category.name }}\n </div>\n\n <div class=\"grid grid-cols-3 gap-3\">\n @for (preset of category.presets; track preset.name) {\n <button (click)=\"selectPreset(preset)\"\n class=\"flex flex-col items-center gap-1 p-2 rounded-lg border border-border hover:bg-surface-container transition-colors text-center\">\n <ngs-icon [name]=\"preset.icon\" class=\"size-6 mb-1\"/>\n <span class=\"text-xs font-medium truncate w-full\">{{ preset.name }}</span>\n <span class=\"text-[10px] text-muted-foreground whitespace-nowrap\">{{ preset.width }}\u00D7{{ preset.height }} px</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n\n @if (effectsPortal()) {\n <div class=\"absolute inset-0 z-20 bg-surface-container-lowest\">\n <ng-container [cdkPortalOutlet]=\"effectsPortal()\"/>\n </div>\n }\n </ngs-tab-panel-aside>\n </ngs-tab-panel>\n </ngs-panel-sidebar>\n <ngs-panel-content class=\"h-full\">\n <ngs-panel class=\"h-full\">\n <ngs-panel-header>\n <ngs-toolbar class=\"h-full px-3 border-b border-border\">\n <button ngsIconButton (click)=\"toggleAside()\">\n <ngs-icon name=\"fluent:navigation-24-regular\"/>\n </button>\n <ngs-toolbar-title>{{ title() }}</ngs-toolbar-title>\n <ngs-toolbar-spacer/>\n <div class=\"flex items-center gap-2\">\n <button ngsIconButton (click)=\"zoomOut()\">\n <ngs-icon name=\"fluent:zoom-out-24-regular\"/>\n </button>\n <span class=\"text-sm font-medium w-12 text-center\">{{ zoomPercentage() }}%</span>\n <button ngsIconButton (click)=\"zoomIn()\">\n <ngs-icon name=\"fluent:zoom-in-24-regular\"/>\n </button>\n </div>\n <ngs-toolbar-spacer/>\n <div class=\"flex\">\n <button ngsIconButton (click)=\"undo()\" [disabled]=\"!designerService.canUndo()\">\n <ngs-icon name=\"fluent:arrow-hook-up-left-24-regular\"/>\n </button>\n <button ngsIconButton (click)=\"redo()\" [disabled]=\"!designerService.canRedo()\">\n <ngs-icon name=\"fluent:arrow-hook-up-right-24-regular\"/>\n </button>\n </div>\n @if (showDownloadButton()) {\n <ngs-divider vertical/>\n <button ngsButton=\"filled\" (click)=\"download()\" class=\"mr-2\">Download</button>\n }\n </ngs-toolbar>\n </ngs-panel-header>\n <ngs-panel-content class=\"bg-surface-container overflow-hidden h-full relative\" (wheel)=\"onWheel($event)\"\n (mousedown)=\"canvasContainer.focus()\"\n (dragover)=\"onDragOver($event)\"\n (drop)=\"onDrop($event)\">\n @if (settingsPortal()) {\n <ng-container [cdkPortalOutlet]=\"settingsPortal()\"/>\n }\n <div #canvasContainer class=\"w-full h-full outline-none\" tabindex=\"0\" (contextmenu)=\"$event.preventDefault()\"></div>\n </ngs-panel-content>\n </ngs-panel>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;width:100%;height:100%}:host ngs-tab-panel{--ngs-tab-panel-aside-width: calc(var(--spacing, .25rem) * 90);--ngs-tab-panel-nav-padding: calc(var(--spacing, .25rem) * 3) 0}:host ngs-list{--ngs-list-padding: 0;--ngs-list-item-radius: 0}:host ngs-tab-group{--ngs-tab-label-padding: 16px 16px}:host .masonry-grid{column-count:2;column-gap:12px}:host .masonry-grid>*{break-inside:avoid;margin-bottom:12px;display:block;width:100%}:host button.is-dragging{background:transparent!important;border-color:transparent!important;box-shadow:none!important;transition:none!important;outline:none!important}:host button.is-dragging:hover{background:transparent!important;border-color:transparent!important;box-shadow:none!important}:host button.is-dragging *{color:currentColor!important}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelSidebar, selector: "ngs-panel-sidebar" }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: Toolbar, selector: "ngs-toolbar", exportAs: ["ngsToolbar"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: ToolbarSpacer, selector: "ngs-toolbar-spacer" }, { kind: "component", type: Divider, selector: "ngs-divider", inputs: ["vertical", "inset", "fixedHeight"], exportAs: ["ngsDivider"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "component", type: ToolbarTitle, selector: "ngs-toolbar-title" }, { kind: "component", type: TabPanel, selector: "ngs-tab-panel", inputs: ["hideContentIfTabNotSelected", "activeItemId", "compact"], outputs: ["itemIdChanged"], exportAs: ["ngsTabPanel"] }, { kind: "component", type: TabPanelAside, selector: "ngs-tab-panel-aside", exportAs: ["ngsTabPanelAside"] }, { kind: "directive", type: TabPanelAsideContentDirective, selector: "[ngsTabPanelAsideContent]", inputs: ["ngsTabPanelAsideContent"], exportAs: ["ngsTabPanelAsideContent"] }, { kind: "component", type: TabPanelContent, selector: "ngs-tab-panel-content", exportAs: ["ngsTabPanelContent"] }, { kind: "component", type: TabPanelItem, selector: "ngs-tab-panel-item", inputs: ["for"], exportAs: ["ngsTabPanelItem"] }, { kind: "directive", type: TabPanelItemIconDirective, selector: "[ngsTabPanelItemIcon]", exportAs: ["ngsTabPanelItemIcon"] }, { kind: "component", type: TabPanelItemText, selector: "ngs-tab-panel-item-text", exportAs: ["ngsTabPanelItemText"] }, { kind: "component", type: TabPanelNav, selector: "ngs-tab-panel-nav", exportAs: ["ngsTabPanelNav"] }, { kind: "component", type: List, selector: "ngs-list", inputs: ["disabled", "disableRipple"], exportAs: ["ngsList"] }, { kind: "component", type: ListItem, selector: "ngs-list-item, a[ngs-list-item], button[ngs-list-item]", inputs: ["disabled", "lines"], exportAs: ["ngsListItem"] }, { kind: "directive", type: ListItemLine, selector: "[ngsListItemLine], [ngsLine]" }, { 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: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "component", type: TabGroup, selector: "ngs-tab-group", inputs: ["selectedIndex", "headerPosition", "preserveContent", "ngs-stretch-tabs", "ngs-align-tabs", "disableRipple", "animationDuration", "animate.enter", "animate.leave"], outputs: ["selectedIndexChange", "selectedTabChange", "focusChange"] }, { kind: "component", type: Tab, selector: "ngs-tab", inputs: ["label", "aria-label", "aria-labelledby", "disabled"], exportAs: ["ngsTab"] }, { kind: "component", type: Accordion, selector: "ngs-accordion", inputs: ["multi", "hideToggle"], exportAs: ["ngsAccordion"] }, { kind: "component", type: ExpansionPanel, selector: "ngs-expansion-panel", inputs: ["disabled", "expanded", "hideToggle"], outputs: ["expandedChange", "opened", "closed"], exportAs: ["ngsExpansionPanel"] }, { kind: "component", type: ExpansionPanelHeader, selector: "ngs-expansion-panel-header", inputs: ["hideToggle"] }, { kind: "component", type: ExpansionPanelTitle, selector: "ngs-expansion-panel-title", exportAs: ["ngsExpansionPanelTitle"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty"], exportAs: ["ngsFormField"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "ariaLabel", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: ColorSwitcher, selector: "ngs-color-switcher", inputs: ["colors", "selectedColor", "disabled"], outputs: ["colorChange"], exportAs: ["ngsColorSwitcher"] }, { kind: "component", type: ScrollbarArea, selector: "ngs-scrollbar-area", inputs: ["scrollbarWidth", "autoHide", "absolute"], outputs: ["scrolled"], exportAs: ["ngsScrollbarArea"] }, { kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "directive", type: UploadTriggerDirective, selector: "[ngsUploadTrigger]", inputs: ["accept", "multiple"], outputs: ["fileSelected"], exportAs: ["ngsUploadTrigger"] }, { kind: "component", type: ProgressSpinner, selector: "ngs-progress-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["ngsProgressSpinner"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
3953
+ ], viewQueries: [{ propertyName: "canvasContainer", first: true, predicate: ["canvasContainer"], descendants: true, isSignal: true }], exportAs: ["ngsImageDesigner"], ngImport: i0, template: "<ngs-panel class=\"h-full\">\n <ngs-panel-sidebar class=\"border-r border-border h-full\">\n <ngs-tab-panel hideContentIfTabNotSelected [activeItemId]=\"activeItemId()\" class=\"h-full\">\n <ngs-tab-panel-content>\n <ngs-tab-panel-nav>\n <ngs-tab-panel-item for=\"text\" (click)=\"activeItemId.set('text')\">\n <ngs-icon name=\"fluent:text-field-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Text</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"photos\" (click)=\"activeItemId.set('photos')\">\n <ngs-icon name=\"fluent:image-copy-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Photos</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"elements\" (click)=\"activeItemId.set('elements')\">\n <ngs-icon name=\"fluent:shape-subtract-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Elements</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"upload\" (click)=\"activeItemId.set('upload')\">\n <ngs-icon name=\"fluent:arrow-upload-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Upload</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"background\" (click)=\"activeItemId.set('background')\">\n <ngs-icon name=\"fluent:grid-dots-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Background</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"layers\" (click)=\"activeItemId.set('layers')\">\n <ngs-icon name=\"fluent:layer-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Layers</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n <ngs-tab-panel-item for=\"resize\" (click)=\"activeItemId.set('resize')\">\n <ngs-icon name=\"fluent:resize-24-regular\" ngsTabPanelItemIcon/>\n <ngs-tab-panel-item-text>Resize</ngs-tab-panel-item-text>\n </ngs-tab-panel-item>\n </ngs-tab-panel-nav>\n </ngs-tab-panel-content>\n <ngs-tab-panel-aside class=\"border-s border-border\">\n <ng-template ngsTabPanelAsideContent=\"text\">\n <ngs-panel>\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">Text</ngs-panel-header>\n <ngs-panel-content class=\"p-4 flex flex-col gap-4\">\n <div class=\"grid grid-cols-1 gap-4 mt-2\">\n <button (click)=\"addHeader()\"\n draggable=\"true\"\n (dragstart)=\"onTextDragStart($event, 'header')\"\n class=\"w-full text-left p-4 border border-border rounded-lg hover:bg-surface-container transition-colors\">\n <div class=\"text-3xl font-bold pointer-events-none\">Add a heading</div>\n </button>\n <button (click)=\"addSubheader()\"\n draggable=\"true\"\n (dragstart)=\"onTextDragStart($event, 'subheader')\"\n class=\"w-full text-left p-3 border border-border rounded-lg hover:bg-surface-container transition-colors\">\n <div class=\"text-xl font-semibold pointer-events-none\">Add a subheading</div>\n </button>\n <button (click)=\"addBodyText()\"\n draggable=\"true\"\n (dragstart)=\"onTextDragStart($event, 'body')\"\n class=\"w-full text-left p-2 border border-border rounded-lg hover:bg-surface-container transition-colors\">\n <div class=\"text-base pointer-events-none\">Add body text</div>\n </button>\n </div>\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"photos\">\n <ngs-panel class=\"h-full flex flex-col\">\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">\n<!-- <ngs-form-field class=\"w-full\" subscriptHiddenIfEmpty>-->\n<!-- <ngs-icon name=\"fluent:search-24-regular\" ngsIconPrefix/>-->\n<!-- <input type=\"text\"-->\n<!-- ngsInput-->\n<!-- placeholder=\"Search photos\"-->\n<!-- [ngModel]=\"photosFilter()\"-->\n<!-- (ngModelChange)=\"photosFilter.set($event)\"/>-->\n<!-- </ngs-form-field>-->\n Photos\n </ngs-panel-header>\n <ngs-panel-content class=\"relative flex-1 p-0 overflow-hidden\">\n <ngs-scrollbar-area (scrolled)=\"onPhotosScroll($event)\" class=\"h-full\">\n <div class=\"p-4\">\n <div class=\"masonry-grid\">\n @for (photo of photos(); track photo.id) {\n <button (click)=\"addImage(photo)\"\n draggable=\"true\"\n (dragstart)=\"onImageDragStart($event, photo)\"\n [style.aspect-ratio]=\"photo.width + '/' + photo.height\"\n class=\"border border-border rounded-lg overflow-hidden hover:ring-2 hover:ring-primary transition-all bg-surface-container\">\n <img [src]=\"photo.thumbUrl || photo.url\"\n [alt]=\"photo.name\"\n class=\"w-full h-auto block pointer-events-none\"/>\n </button>\n }\n </div>\n @if (isLoadingPhotos()) {\n <div class=\"flex justify-center p-4\">\n <ngs-progress-spinner diameter=\"32\"/>\n </div>\n }\n @if (photos().length === 0 && !isLoadingPhotos()) {\n <div class=\"text-center p-4 text-sm text-neutral-500\">\n No photos found\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"elements\">\n <ngs-panel>\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">Elements</ngs-panel-header>\n <ngs-panel-content class=\"p-4 overflow-y-auto\">\n <div class=\"grid grid-cols-3 gap-3\">\n @for (element of elements(); track element.name) {\n <button (click)=\"addShape(element)\"\n draggable=\"true\"\n (dragstart)=\"onImageDragStart($event, element)\"\n class=\"aspect-square p-2 border border-border rounded-lg hover:bg-surface-container\n transition-colors flex items-center justify-center\">\n <img [src]=\"element.data\" [alt]=\"element.name\"\n class=\"max-w-full max-h-full object-contain pointer-events-none\"/>\n </button>\n }\n </div>\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"upload\">\n <ngs-panel class=\"h-full flex flex-col\">\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">\n <ngs-toolbar>\n <div>Upload</div>\n <ngs-toolbar-spacer/>\n <button ngsButton=\"filled\"\n ngsUploadTrigger\n [accept]=\"'image/*'\"\n (fileSelected)=\"onFileSelected($event)\"\n [loading]=\"isUploading()\">\n <ngs-icon name=\"fluent:arrow-upload-24-regular\"/>\n Add Image\n </button>\n </ngs-toolbar>\n </ngs-panel-header>\n <ngs-panel-content>\n @if (uploadedImages().length > 0 || assets().length > 0) {\n <ngs-scrollbar-area (scrolled)=\"onAssetsScroll($event)\">\n <div class=\"p-4 flex flex-col gap-4 min-h-full\">\n @if (isUploading() || uploadPreview()) {\n <div class=\"relative aspect-video border border-border rounded-lg overflow-hidden bg-surface-container\">\n @if (uploadPreview()) {\n <img [src]=\"uploadPreview()\" class=\"w-full h-full object-contain\" alt=\"Preview\"/>\n }\n @if (isUploading()) {\n <div class=\"absolute inset-0 bg-black/20 flex items-center justify-center backdrop-blur-[2px]\">\n <div class=\"flex flex-col items-center gap-2\">\n <ngs-progress-spinner diameter=\"32\" color=\"white\"/>\n <span class=\"text-xs font-medium text-white\">Uploading...</span>\n </div>\n </div>\n }\n </div>\n }\n\n @if (uploadedImages().length > 0) {\n <div class=\"flex flex-col gap-2\">\n <div class=\"text-sm font-semibold\">Your Uploads</div>\n <div class=\"masonry-grid\">\n @for (photo of uploadedImages(); track $index) {\n <button (click)=\"addUploadedImage(photo)\"\n draggable=\"true\"\n (dragstart)=\"onImageDragStart($event, photo)\"\n [style.aspect-ratio]=\"photo.width + '/' + photo.height\"\n class=\"border border-border rounded-lg overflow-hidden hover:ring-2\n hover:ring-primary transition-all bg-surface-container\">\n <img [src]=\"photo.url\" class=\"w-full h-auto block pointer-events-none\" alt=\"Uploaded image\"/>\n </button>\n }\n </div>\n </div>\n }\n\n @if (assets().length > 0) {\n <div class=\"flex flex-col gap-2\">\n <div class=\"text-sm font-semibold\">Assets</div>\n <div class=\"masonry-grid\">\n @for (asset of assets(); track asset.id) {\n <button (click)=\"addImage(asset)\"\n draggable=\"true\"\n (dragstart)=\"onImageDragStart($event, asset)\"\n [style.aspect-ratio]=\"asset.width + '/' + asset.height\"\n class=\"border border-border rounded-lg overflow-hidden hover:ring-2\n hover:ring-primary transition-all bg-surface-container\">\n <img [src]=\"asset.thumbUrl || asset.url\"\n [alt]=\"asset.name\"\n class=\"w-full h-auto block pointer-events-none\"/>\n </button>\n }\n </div>\n @if (isLoadingAssets()) {\n <div class=\"flex justify-center p-4\">\n <ngs-progress-spinner diameter=\"32\"/>\n </div>\n }\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n }\n\n @if (uploadedImages().length === 0 && assets().length === 0 && !isLoadingAssets()) {\n <div class=\"h-full flex items-center justify-center\">\n <div class=\"text-sm text-neutral-500\">No images uploaded yet.</div>\n </div>\n }\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"background\">\n <ngs-panel>\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">Background</ngs-panel-header>\n <ngs-panel-content>\n <ngs-tab-group animationDuration=\"0\" class=\"h-full\">\n <ngs-tab label=\"Color\">\n <div class=\"absolute inset-0\">\n <ngs-scrollbar-area>\n <div class=\"p-4\">\n <ngs-accordion [multi]=\"false\">\n <ngs-expansion-panel [expanded]=\"true\">\n <ngs-expansion-panel-header>\n <ngs-expansion-panel-title>Colors</ngs-expansion-panel-title>\n </ngs-expansion-panel-header>\n <div class=\"py-2\">\n <ngs-color-switcher [colors]=\"presetColors()\" (colorChange)=\"setColor($event)\"/>\n </div>\n </ngs-expansion-panel>\n <ngs-expansion-panel>\n <ngs-expansion-panel-header>\n <ngs-expansion-panel-title>Gradients</ngs-expansion-panel-title>\n </ngs-expansion-panel-header>\n <div class=\"grid grid-cols-3 gap-3 py-2\">\n @for (gradient of presetGradients(); track $index) {\n <button (click)=\"setGradient(gradient)\"\n [style.background]=\"gradient.css\"\n class=\"aspect-[16/6.75] w-full rounded-lg border border-border hover:ring-2 hover:ring-primary transition-all\">\n </button>\n }\n </div>\n </ngs-expansion-panel>\n </ngs-accordion>\n </div>\n </ngs-scrollbar-area>\n </div>\n </ngs-tab>\n <ngs-tab label=\"Patterns\">\n <div class=\"absolute inset-0\">\n <ngs-scrollbar-area>\n <div class=\"p-4\">\n <div class=\"grid grid-cols-3 gap-3\">\n @for (pattern of presetPatterns(); track $index) {\n <button (click)=\"addPattern(pattern)\"\n class=\"aspect-square w-full rounded-lg border border-border hover:ring-2 hover:ring-primary transition-all overflow-hidden\">\n <img [src]=\"pattern\" class=\"w-full h-full object-cover\" alt=\"pattern\">\n </button>\n }\n </div>\n </div>\n </ngs-scrollbar-area>\n </div>\n </ngs-tab>\n </ngs-tab-group>\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"layers\">\n <ngs-panel>\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">Layers</ngs-panel-header>\n <ngs-panel-content>\n @if (layersFromService().length > 0) {\n <ngs-list cdkDropList (cdkDropListDropped)=\"drop($event)\">\n @for (layer of layersFromService(); track layer.id) {\n <ngs-list-item cdkDrag\n cdkDragLockAxis=\"y\"\n [cdkDragStartDelay]=\"100\"\n (click)=\"selectLayer(layer.id!)\"\n [class.is-active]=\"selectedLayerId() === layer.id\"\n class=\"relative group bg-surface\">\n <ngs-icon [name]=\"getLayerIcon(layer.type)\" ngsListItemIcon/>\n <div ngsListItemLine>\n {{ layer.name || layer.type }}\n <!-- {{ layer.text || '' }}-->\n </div>\n <div class=\"absolute right-2 top-1/2 -translate-y-1/2 flex items-center\">\n <button ngsIconButton (click)=\"toggleLayerVisibility(layer.id!, $event)\">\n <ngs-icon\n [name]=\"layer.visible === false ? 'fluent:eye-off-24-regular' : 'fluent:eye-24-regular'\"/>\n </button>\n <button ngsIconButton (click)=\"toggleLayerLock(layer.id!, $event)\">\n <ngs-icon\n [name]=\"layer.locked ? 'fluent:lock-closed-24-regular' : 'fluent:lock-open-24-regular'\"\n class=\"size-5\"/>\n </button>\n <button ngsIconButton (click)=\"deleteLayer(layer.id!, $event)\" [disabled]=\"layer.locked\">\n <ngs-icon name=\"fluent:delete-24-regular\" class=\"size-5\"/>\n </button>\n </div>\n\n <div *cdkDragPlaceholder\n class=\"min-h-[var(--ngs-list-item-min-height)] bg-surface-container\"></div>\n </ngs-list-item>\n }\n </ngs-list>\n } @else {\n <div class=\"text-sm p-4 h-full flex items-center justify-center\">No layers available</div>\n }\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n <ng-template ngsTabPanelAsideContent=\"resize\">\n <ngs-panel>\n <ngs-panel-header class=\"border-b border-b-border flex items-center px-4\">Resize</ngs-panel-header>\n <ngs-panel-content>\n <ngs-scrollbar-area>\n <div class=\"p-4 flex flex-col gap-4\">\n <div class=\"flex flex-col gap-4\">\n <ngs-form-field subscriptHiddenIfEmpty>\n <ngs-label>Width (px)</ngs-label>\n <input ngsInput type=\"number\" [(ngModel)]=\"resizeWidth\">\n </ngs-form-field>\n\n <ngs-form-field subscriptHiddenIfEmpty>\n <ngs-label>Height (px)</ngs-label>\n <input ngsInput type=\"number\" [(ngModel)]=\"resizeHeight\">\n </ngs-form-field>\n\n <ngs-form-field subscriptHiddenIfEmpty>\n <ngs-label>Units</ngs-label>\n <ngs-select [(ngModel)]=\"resizeUnit\">\n <ngs-option value=\"px\">px</ngs-option>\n </ngs-select>\n </ngs-form-field>\n\n <button ngsButton=\"filled\" fullWidth (click)=\"applyResize()\">Resize</button>\n </div>\n\n <div class=\"flex flex-col gap-6 mt-2\">\n @for (category of presetCategories(); track category.name) {\n <div class=\"flex flex-col gap-3\">\n <div class=\"flex items-center gap-2 font-semibold text-sm\">\n <ngs-icon [name]=\"category.icon\" class=\"size-5\"/>\n {{ category.name }}\n </div>\n\n <div class=\"grid grid-cols-3 gap-3\">\n @for (preset of category.presets; track preset.name) {\n <button (click)=\"selectPreset(preset)\"\n class=\"flex flex-col items-center gap-1 p-2 rounded-lg border border-border hover:bg-surface-container transition-colors text-center\">\n <ngs-icon [name]=\"preset.icon\" class=\"size-6 mb-1\"/>\n <span class=\"text-xs font-medium truncate w-full\">{{ preset.name }}</span>\n <span class=\"text-[10px] text-muted-foreground whitespace-nowrap\">{{ preset.width }}\u00D7{{ preset.height }} px</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n </ngs-panel>\n </ng-template>\n\n @if (effectsPortal()) {\n <div class=\"absolute inset-0 z-20 bg-surface-container-lowest\">\n <ng-container [cdkPortalOutlet]=\"effectsPortal()\"/>\n </div>\n }\n </ngs-tab-panel-aside>\n </ngs-tab-panel>\n </ngs-panel-sidebar>\n <ngs-panel-content class=\"h-full\">\n <ngs-panel class=\"h-full\">\n <ngs-panel-header>\n <ngs-toolbar class=\"h-full px-3 border-b border-border\">\n <button ngsIconButton (click)=\"toggleAside()\">\n <ngs-icon name=\"fluent:navigation-24-regular\"/>\n </button>\n <ngs-toolbar-title>{{ title() }}</ngs-toolbar-title>\n <ngs-toolbar-spacer/>\n <div class=\"flex items-center gap-2\">\n <button ngsIconButton (click)=\"zoomOut()\">\n <ngs-icon name=\"fluent:zoom-out-24-regular\"/>\n </button>\n <span class=\"text-sm font-medium w-12 text-center\">{{ zoomPercentage() }}%</span>\n <button ngsIconButton (click)=\"zoomIn()\">\n <ngs-icon name=\"fluent:zoom-in-24-regular\"/>\n </button>\n </div>\n <ngs-toolbar-spacer/>\n <div class=\"flex\">\n <button ngsIconButton (click)=\"undo()\" [disabled]=\"!designerService.canUndo()\">\n <ngs-icon name=\"fluent:arrow-hook-up-left-24-regular\"/>\n </button>\n <button ngsIconButton (click)=\"redo()\" [disabled]=\"!designerService.canRedo()\">\n <ngs-icon name=\"fluent:arrow-hook-up-right-24-regular\"/>\n </button>\n </div>\n @if (showDownloadButton()) {\n <ngs-divider vertical/>\n <button ngsButton=\"filled\" (click)=\"download()\" class=\"mr-2\">Download</button>\n }\n </ngs-toolbar>\n </ngs-panel-header>\n <ngs-panel-content class=\"bg-surface-container overflow-hidden h-full relative\" (wheel)=\"onWheel($event)\"\n (mousedown)=\"canvasContainer.focus()\"\n (dragover)=\"onDragOver($event)\"\n (drop)=\"onDrop($event)\">\n @if (settingsPortal()) {\n <ng-container [cdkPortalOutlet]=\"settingsPortal()\"/>\n }\n <div #canvasContainer class=\"w-full h-full outline-none\" tabindex=\"0\" (contextmenu)=\"$event.preventDefault()\"></div>\n </ngs-panel-content>\n </ngs-panel>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;width:100%;height:100%}:host ngs-tab-panel{--ngs-tab-panel-aside-width: calc(var(--spacing, .25rem) * 90);--ngs-tab-panel-nav-padding: calc(var(--spacing, .25rem) * 3) 0}:host ngs-list{--ngs-list-padding: 0;--ngs-list-item-radius: 0}:host ngs-tab-group{--ngs-tab-label-padding: 16px 16px}:host .masonry-grid{column-count:2;column-gap:12px}:host .masonry-grid>*{break-inside:avoid;margin-bottom:12px;display:block;width:100%}:host button.is-dragging{background:transparent!important;border-color:transparent!important;box-shadow:none!important;transition:none!important;outline:none!important}:host button.is-dragging:hover{background:transparent!important;border-color:transparent!important;box-shadow:none!important}:host button.is-dragging *{color:currentColor!important}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelSidebar, selector: "ngs-panel-sidebar" }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: Toolbar, selector: "ngs-toolbar", exportAs: ["ngsToolbar"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: ToolbarSpacer, selector: "ngs-toolbar-spacer" }, { kind: "component", type: Divider, selector: "ngs-divider", inputs: ["vertical", "inset", "fixedHeight"], exportAs: ["ngsDivider"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "component", type: ToolbarTitle, selector: "ngs-toolbar-title" }, { kind: "component", type: TabPanel, selector: "ngs-tab-panel", inputs: ["hideContentIfTabNotSelected", "activeItemId", "compact"], outputs: ["itemIdChanged"], exportAs: ["ngsTabPanel"] }, { kind: "component", type: TabPanelAside, selector: "ngs-tab-panel-aside", exportAs: ["ngsTabPanelAside"] }, { kind: "directive", type: TabPanelAsideContentDirective, selector: "[ngsTabPanelAsideContent]", inputs: ["ngsTabPanelAsideContent"], exportAs: ["ngsTabPanelAsideContent"] }, { kind: "component", type: TabPanelContent, selector: "ngs-tab-panel-content", exportAs: ["ngsTabPanelContent"] }, { kind: "component", type: TabPanelItem, selector: "ngs-tab-panel-item", inputs: ["for"], exportAs: ["ngsTabPanelItem"] }, { kind: "directive", type: TabPanelItemIconDirective, selector: "[ngsTabPanelItemIcon]", exportAs: ["ngsTabPanelItemIcon"] }, { kind: "component", type: TabPanelItemText, selector: "ngs-tab-panel-item-text", exportAs: ["ngsTabPanelItemText"] }, { kind: "component", type: TabPanelNav, selector: "ngs-tab-panel-nav", exportAs: ["ngsTabPanelNav"] }, { kind: "component", type: List, selector: "ngs-list", inputs: ["disabled", "disableRipple"], exportAs: ["ngsList"] }, { kind: "component", type: ListItem, selector: "ngs-list-item, a[ngs-list-item], button[ngs-list-item]", inputs: ["disabled", "lines"], exportAs: ["ngsListItem"] }, { kind: "directive", type: ListItemLine, selector: "[ngsListItemLine], [ngsLine]" }, { 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: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "component", type: TabGroup, selector: "ngs-tab-group", inputs: ["selectedIndex", "headerPosition", "preserveContent", "ngs-stretch-tabs", "ngs-align-tabs", "disableRipple", "animationDuration", "animate.enter", "animate.leave"], outputs: ["selectedIndexChange", "selectedTabChange", "focusChange"] }, { kind: "component", type: Tab, selector: "ngs-tab", inputs: ["label", "aria-label", "aria-labelledby", "disabled"], exportAs: ["ngsTab"] }, { kind: "component", type: Accordion, selector: "ngs-accordion", inputs: ["multi", "hideToggle"], exportAs: ["ngsAccordion"] }, { kind: "component", type: ExpansionPanel, selector: "ngs-expansion-panel", inputs: ["disabled", "expanded", "hideToggle"], outputs: ["expandedChange", "opened", "closed"], exportAs: ["ngsExpansionPanel"] }, { kind: "component", type: ExpansionPanelHeader, selector: "ngs-expansion-panel-header", inputs: ["hideToggle"] }, { kind: "component", type: ExpansionPanelTitle, selector: "ngs-expansion-panel-title", exportAs: ["ngsExpansionPanelTitle"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty"], exportAs: ["ngsFormField"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "ariaLabel", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: ColorSwitcher, selector: "ngs-color-switcher", inputs: ["colors", "selectedColor", "disabled"], outputs: ["colorChange"], exportAs: ["ngsColorSwitcher"] }, { kind: "component", type: ScrollbarArea, selector: "ngs-scrollbar-area", inputs: ["scrollbarWidth", "autoHide", "absolute"], outputs: ["scrolled"], exportAs: ["ngsScrollbarArea"] }, { kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "directive", type: UploadTriggerDirective, selector: "[ngsUploadTrigger]", inputs: ["accept", "multiple"], outputs: ["fileSelected"], exportAs: ["ngsUploadTrigger"] }, { kind: "component", type: ProgressSpinner, selector: "ngs-progress-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["ngsProgressSpinner"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
3954
3954
  }
3955
3955
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ImageDesigner, decorators: [{
3956
3956
  type: Component,