shirkasoft-ui-components 1.1.1 → 1.1.3

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,5 +1,5 @@
1
1
  import * as i1 from '@angular/common';
2
- import { CommonModule, isPlatformBrowser } from '@angular/common';
2
+ import { CommonModule, DOCUMENT, isPlatformBrowser } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
4
  import { inject, ChangeDetectorRef, input, output, viewChild, signal, computed, DestroyRef, effect, forwardRef, ChangeDetectionStrategy, Component, Injectable, model, ElementRef, HostListener, ApplicationRef, EnvironmentInjector, createComponent, ViewContainerRef, untracked, PLATFORM_ID, Inject, Directive, contentChildren } from '@angular/core';
5
5
  import * as i2 from '@angular/forms';
@@ -7,9 +7,9 @@ import { ReactiveFormsModule, NG_VALUE_ACCESSOR, FormsModule, FormControl, FormG
7
7
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
8
8
  import * as i1$1 from '@jsverse/transloco';
9
9
  import { TranslocoService, TranslocoModule } from '@jsverse/transloco';
10
- import { LucideEyeOff, LucideEye, LucideSearch, LucideDynamicIcon, LucideX, LucideTriangleAlert, LucideInfo, LucideCircleX, LucideCheck, LucideLoader, LucideTrash2, LucideLoaderCircle, LucideMinimize2, LucideMaximize2, LucideChevronRight, LucideChevronLeft, LucideChevronDown, LucideFile, LucideUpload, LucideList, LucideWarehouse, LucideUserCog, LucideShieldCheck, LucideCalculator, LucidePercent, LucideDollarSign, LucideLineChart, LucideBot, LucideReceipt, LucideUsers, LucideTruck, LucideTag, LucideTags, LucideBox, LucidePackage, LucideLandmark, LucideBuilding, LucideUserPlus, LucideUserRoundX, LucideUserCheck, LucideUser, LucideLogOut, LucideSettings, LucideClock, LucideStar, LucideVideo, LucidePlay, LucideShare2, LucideCopy, LucideCircleCheck, LucideArchive, LucideFileText, LucidePresentation, LucideFileSpreadsheet, LucideRefreshCw, LucideCirclePlus, LucidePlus, LucideDownload, LucideToggleLeft, LucideToggleRight, LucideUserPen, LucideKey, LucidePencil, LucideFunnel, LucideInbox, LucideArrowUpDown, LucideArrowDown, LucideArrowUp, LucideChevronsRight, LucideChevronsLeft, LucideBarChart3, LucideMenu, LucideBuilding2, LucideLayoutDashboard, LucideHome, LucideHouse, LucideBanknote, LucidePanelRight, LucideGlobe, LucideCalendarCheck, LucideCalendar, LucideSliders, LucideWallet, LucideBriefcase, LucideIdCard, LucideCreditCard, LucideChartLine, LucideChartPie, LucidePuzzle, LucideLayers, LucideShield, LucideUserRound, LucideMapPin, LucideUtensils, LucideShip, LucideBus, LucideMountain, LucideCar, LucideTicket, LucideHotel, LucideCalendarDays, LucideCircleHelp, LucideCircleAlert, LucideStickyNote, LucideCoins, LucideMap, LucideClipboardCheck, LucideScrollText, LucideArrowLeft } from '@lucide/angular';
11
- import { Router } from '@angular/router';
12
- import { Subject, debounceTime } from 'rxjs';
10
+ import { LucideEyeOff, LucideEye, LucideSearch, LucideDynamicIcon, LucideX, LucideTriangleAlert, LucideInfo, LucideCircleX, LucideCheck, LucideLoader, LucideTrash2, LucideLoaderCircle, LucideMinimize2, LucideMaximize2, LucideChevronRight, LucideChevronLeft, LucideChevronDown, LucideFile, LucideUpload, LucideArrowDown, LucideArrowUp, LucideArrowLeft, LucideArrowRight, LucideFilter, LucideCalculator, LucideMoreHorizontal, LucideMenu, LucideCircleCheck, LucideGlobe, LucideWallet, LucidePercent, LucideDollarSign, LucideCreditCard, LucideTag, LucideTruck, LucidePackage, LucideShoppingCart, LucideFolderOpen, LucideFileText, LucideHome, LucideMapPin, LucidePhone, LucideMail, LucideLock, LucideChevronUp, LucideSparkles, LucideCopy, LucideRocket, LucideRefreshCw, LucideDownload, LucideAlertCircle, LucideAlertTriangle, LucideSettings, LucideClock, LucideCalendarCheck, LucideCalendar, LucideStar, LucideUsers, LucideUser, LucideSave, LucidePencil, LucidePlus, LucideCircleHelp, LucideList, LucideWarehouse, LucideUserCog, LucideShieldCheck, LucideLineChart, LucideBot, LucideReceipt, LucideTags, LucideBox, LucideLandmark, LucideBuilding, LucideUserPlus, LucideUserRoundX, LucideUserCheck, LucideLogOut, LucideVideo, LucidePlay, LucideShare2, LucideArchive, LucidePresentation, LucideFileSpreadsheet, LucideCirclePlus, LucideToggleLeft, LucideToggleRight, LucideUserPen, LucideKey, LucideFunnel, LucideInbox, LucideArrowUpDown, LucideChevronsRight, LucideChevronsLeft, LucideBarChart3, LucideBuilding2, LucideLayoutDashboard, LucideHouse, LucideBanknote, LucidePanelRight, LucideSliders, LucideBriefcase, LucideIdCard, LucideChartLine, LucideChartPie, LucidePuzzle, LucideLayers, LucideShield, LucideUserRound, LucideUtensils, LucideShip, LucideBus, LucideMountain, LucideCar, LucideTicket, LucideHotel, LucideCalendarDays, LucideCircleAlert, LucideStickyNote, LucideCoins, LucideMap, LucideClipboardCheck, LucideScrollText } from '@lucide/angular';
11
+ import { Router, RouterLink } from '@angular/router';
12
+ import { Subject, debounceTime, BehaviorSubject } from 'rxjs';
13
13
  import { Chart, registerables } from 'chart.js';
14
14
 
15
15
  const icons$9 = {
@@ -38,7 +38,7 @@ class TextFieldComponent {
38
38
  this.textAreaHeight = input('h-28', ...(ngDevMode ? [{ debugName: "textAreaHeight" }] : /* istanbul ignore next */ []));
39
39
  this.searchMode = input(false, ...(ngDevMode ? [{ debugName: "searchMode" }] : /* istanbul ignore next */ []));
40
40
  this.displayValue = input('', ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
41
- this.autocomplete = input('new-password', ...(ngDevMode ? [{ debugName: "autocomplete" }] : /* istanbul ignore next */ []));
41
+ this.autocomplete = input('', ...(ngDevMode ? [{ debugName: "autocomplete" }] : /* istanbul ignore next */ []));
42
42
  this.searchButtonClick = output();
43
43
  this.getIcon = (name) => icons$9[name] ?? LucideEye;
44
44
  this.inputElement = viewChild('input', ...(ngDevMode ? [{ debugName: "inputElement" }] : /* istanbul ignore next */ []));
@@ -2149,6 +2149,276 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImpo
2149
2149
  args: [{ selector: 'shk-file-upload', standalone: true, imports: [CommonModule, LucideDynamicIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-2\">\n @if (label()) {\n <label class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-300)]\">\n {{ label() }}\n </label>\n }\n\n @if (canAddMore()) {\n <div\n class=\"relative border-2 border-dashed _shk-border dark:[border-color:var(--shk-surface-700)] rounded-lg p-4 transition-colors hover:[border-color:var(--shk-primary-400)] dark:hover:[border-color:var(--shk-primary-500)]\"\n [class.border-red-400]=\"errorMessage()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n >\n <input\n #fileInput\n type=\"file\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n (change)=\"onFileChange($event)\"\n class=\"hidden\"\n />\n\n <div\n class=\"flex flex-col items-center gap-2 cursor-pointer\"\n (click)=\"fileInput.click()\"\n [class.drag-over]=\"isDragOver()\"\n >\n <svg [lucideIcon]=\"getIcon('upload')\" class=\"w-8 h-8 _shk-icon-subtle dark:[color:var(--shk-surface-600)]\"></svg>\n <span class=\"text-sm _shk-text-surface-700 dark:[color:var(--shk-surface-300)]\">\n {{ fileUploadText() }}\n </span>\n @if (fileRecommendation()) {\n <span class=\"text-xs _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\">\n {{ fileRecommendation() }}\n </span>\n }\n </div>\n </div>\n }\n\n @if (maxReached()) {\n <div class=\"text-xs _shk-text-surface-400 dark:[color:var(--shk-surface-500)] text-center py-2\">\n M\u00E1ximo {{ maxFiles() }} archivos alcanzado\n </div>\n }\n\n @if (selectedFiles().length > 0) {\n <div class=\"flex flex-wrap gap-3\">\n @for (file of selectedFiles(); track file.name + file.size + file.lastModified) {\n <div class=\"relative group\">\n @if (isImageFile(file)) {\n <img\n [src]=\"previewUrl(file)\"\n [alt]=\"file.name\"\n class=\"w-20 h-20 object-cover rounded-lg _shk-border dark:[border-color:var(--shk-surface-700)]\"\n />\n } @else {\n <div class=\"w-20 h-20 rounded-lg _shk-border dark:[border-color:var(--shk-surface-700)] flex flex-col items-center justify-center _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] gap-1\">\n <svg [lucideIcon]=\"getIcon('file')\" class=\"w-6 h-6 _shk-text-surface-400\"></svg>\n <span class=\"text-[10px] _shk-text-surface-400 truncate max-w-[70px] px-1 text-center\">{{ file.name }}</span>\n </div>\n }\n <button\n type=\"button\"\n (click)=\"removeFile(file)\"\n class=\"absolute -top-1.5 -right-1.5 w-5 h-5 bg-red-500 text-white rounded-full text-xs flex items-center justify-center opacity-0 group-hover:opacity-100 transition shadow\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3 h-3\"></svg>\n </button>\n </div>\n }\n </div>\n @if (!multiple()) {\n <button\n type=\"button\"\n (click)=\"changeFiles()\"\n class=\"text-xs _shk-text-primary hover:[color:var(--shk-primary-700)] dark:[color:var(--shk-primary-400)] dark:hover:[color:var(--shk-primary-300)] underline cursor-pointer bg-transparent border-0\"\n >\n {{ changeFilesText() }}\n </button>\n }\n }\n\n @if (errorMessage()) {\n <p class=\"text-xs _shk-text-red-500\">{{ errorMessage() }}</p>\n }\n</div>" }]
2150
2150
  }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], fileUploadText: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileUploadText", required: false }] }], fileRecommendation: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileRecommendation", required: false }] }], changeFilesText: [{ type: i0.Input, args: [{ isSignal: true, alias: "changeFilesText", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }], fileRemoved: [{ type: i0.Output, args: ["fileRemoved"] }], fileError: [{ type: i0.Output, args: ["fileError"] }], fileWarning: [{ type: i0.Output, args: ["fileWarning"] }] } });
2151
2151
 
2152
+ const ICONS$2 = {
2153
+ plus: LucidePlus,
2154
+ 'circle-plus': LucidePlus,
2155
+ pencil: LucidePencil,
2156
+ edit: LucidePencil,
2157
+ 'trash-2': LucideTrash2,
2158
+ delete: LucideTrash2,
2159
+ save: LucideSave,
2160
+ x: LucideX,
2161
+ close: LucideX,
2162
+ check: LucideCheck,
2163
+ search: LucideSearch,
2164
+ user: LucideUser,
2165
+ users: LucideUsers,
2166
+ star: LucideStar,
2167
+ calendar: LucideCalendar,
2168
+ 'calendar-check': LucideCalendarCheck,
2169
+ clock: LucideClock,
2170
+ settings: LucideSettings,
2171
+ 'alert-triangle': LucideAlertTriangle,
2172
+ warning: LucideAlertTriangle,
2173
+ 'alert-circle': LucideAlertCircle,
2174
+ info: LucideInfo,
2175
+ download: LucideDownload,
2176
+ upload: LucideUpload,
2177
+ refresh: LucideRefreshCw,
2178
+ rocket: LucideRocket,
2179
+ copy: LucideCopy,
2180
+ sparkles: LucideSparkles,
2181
+ 'chevron-down': LucideChevronDown,
2182
+ 'chevron-up': LucideChevronUp,
2183
+ 'chevron-left': LucideChevronLeft,
2184
+ 'chevron-right': LucideChevronRight,
2185
+ eye: LucideEye,
2186
+ 'eye-off': LucideEyeOff,
2187
+ lock: LucideLock,
2188
+ mail: LucideMail,
2189
+ phone: LucidePhone,
2190
+ 'map-pin': LucideMapPin,
2191
+ home: LucideHome,
2192
+ 'file-text': LucideFileText,
2193
+ 'folder-open': LucideFolderOpen,
2194
+ 'shopping-cart': LucideShoppingCart,
2195
+ package: LucidePackage,
2196
+ truck: LucideTruck,
2197
+ tag: LucideTag,
2198
+ 'credit-card': LucideCreditCard,
2199
+ 'dollar-sign': LucideDollarSign,
2200
+ percent: LucidePercent,
2201
+ wallet: LucideWallet,
2202
+ globe: LucideGlobe,
2203
+ 'circle-check': LucideCircleCheck,
2204
+ 'circle-x': LucideCircleX,
2205
+ menu: LucideMenu,
2206
+ 'more-horizontal': LucideMoreHorizontal,
2207
+ calculator: LucideCalculator,
2208
+ filter: LucideFilter,
2209
+ 'arrow-right': LucideArrowRight,
2210
+ 'arrow-left': LucideArrowLeft,
2211
+ 'arrow-up': LucideArrowUp,
2212
+ 'arrow-down': LucideArrowDown,
2213
+ };
2214
+ /** Resolves a lucide icon name to its component. Falls back to a help circle. */
2215
+ function getIcon$6(name) {
2216
+ if (!name)
2217
+ return null;
2218
+ return ICONS$2[name] ?? LucideCircleHelp;
2219
+ }
2220
+
2221
+ const SEVERITY_CLASS = {
2222
+ secondary: '_shk-tag-secondary',
2223
+ success: '_shk-tag-success',
2224
+ info: '_shk-tag-info',
2225
+ warning: '_shk-tag-warn',
2226
+ danger: '_shk-tag-danger',
2227
+ contrast: '_shk-tag-contrast',
2228
+ };
2229
+ /** Small label used to highlight a value, status or category. */
2230
+ class TagComponent {
2231
+ constructor() {
2232
+ /** Text shown inside the tag. */
2233
+ this.value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
2234
+ /** Color variant. Defaults to 'secondary'. */
2235
+ this.severity = input('secondary', ...(ngDevMode ? [{ debugName: "severity" }] : /* istanbul ignore next */ []));
2236
+ /** Renders the tag with fully rounded corners (pill). */
2237
+ this.rounded = input(false, ...(ngDevMode ? [{ debugName: "rounded" }] : /* istanbul ignore next */ []));
2238
+ /** Optional leading lucide icon name. */
2239
+ this.icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
2240
+ /** Inline styles applied to the tag. */
2241
+ this.style = input({}, ...(ngDevMode ? [{ debugName: "style" }] : /* istanbul ignore next */ []));
2242
+ /** Extra CSS classes. */
2243
+ this.styleClass = input('', ...(ngDevMode ? [{ debugName: "styleClass" }] : /* istanbul ignore next */ []));
2244
+ this.getIcon = getIcon$6;
2245
+ this.tagClass = computed(() => {
2246
+ const base = 'inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium';
2247
+ const severity = SEVERITY_CLASS[this.severity()] ?? SEVERITY_CLASS.secondary;
2248
+ const radius = this.rounded() ? 'rounded-full' : 'rounded-md';
2249
+ return `${base} ${radius} ${severity} ${this.styleClass()}`.trim();
2250
+ }, ...(ngDevMode ? [{ debugName: "tagClass" }] : /* istanbul ignore next */ []));
2251
+ }
2252
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TagComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2253
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: TagComponent, isStandalone: true, selector: "shk-tag", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, severity: { classPropertyName: "severity", publicName: "severity", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, style: { classPropertyName: "style", publicName: "style", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2254
+ <span [class]="tagClass()" [style]="style()">
2255
+ @if (icon()) {
2256
+ <svg [lucideIcon]="getIcon(icon())" class="shk-tag-icon"></svg>
2257
+ }
2258
+ @if (value() !== undefined && value() !== null) {
2259
+ <span>{{ value() }}</span>
2260
+ }
2261
+ <ng-content></ng-content>
2262
+ </span>
2263
+ `, isInline: true, styles: [".shk-tag-icon{width:.875rem;height:.875rem;flex-shrink:0}\n"], dependencies: [{ kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2264
+ }
2265
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TagComponent, decorators: [{
2266
+ type: Component,
2267
+ args: [{ selector: 'shk-tag', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [LucideDynamicIcon], template: `
2268
+ <span [class]="tagClass()" [style]="style()">
2269
+ @if (icon()) {
2270
+ <svg [lucideIcon]="getIcon(icon())" class="shk-tag-icon"></svg>
2271
+ }
2272
+ @if (value() !== undefined && value() !== null) {
2273
+ <span>{{ value() }}</span>
2274
+ }
2275
+ <ng-content></ng-content>
2276
+ </span>
2277
+ `, styles: [".shk-tag-icon{width:.875rem;height:.875rem;flex-shrink:0}\n"] }]
2278
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], severity: [{ type: i0.Input, args: [{ isSignal: true, alias: "severity", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], style: [{ type: i0.Input, args: [{ isSignal: true, alias: "style", required: false }] }], styleClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "styleClass", required: false }] }] } });
2279
+
2280
+ const TOOLTIP_Z_INDEX = '2147483647';
2281
+ /**
2282
+ * Tooltip que se renderiza en `document.body` con `position: fixed`, de modo
2283
+ * que siempre flota por encima de cualquier contenedor (tablas con
2284
+ * `overflow: hidden`, modales, scroll wrappers, etc.) y nunca queda
2285
+ * recortado ni pasa por debajo de su elemento ancla.
2286
+ */
2287
+ class TooltipComponent {
2288
+ constructor() {
2289
+ this.text = input('', ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
2290
+ this.position = input('bottom', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
2291
+ this.mode = input('hover', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
2292
+ this.offset = input(8, ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
2293
+ this.host = inject((ElementRef));
2294
+ this.document = inject(DOCUMENT);
2295
+ this.tooltipEl = null;
2296
+ this.cleanup = null;
2297
+ }
2298
+ onMouseEnter() {
2299
+ if (!this.text())
2300
+ return;
2301
+ const trigger = this.host.nativeElement.querySelector('.shk-tooltip-trigger');
2302
+ if (!trigger)
2303
+ return;
2304
+ this.show(trigger);
2305
+ }
2306
+ onMouseLeave() {
2307
+ this.hide();
2308
+ }
2309
+ show(trigger) {
2310
+ this.removeTooltip();
2311
+ const el = this.document.createElement('span');
2312
+ el.className = 'shk-tooltip-dynamic';
2313
+ el.textContent = this.text();
2314
+ Object.assign(el.style, {
2315
+ position: 'fixed',
2316
+ zIndex: TOOLTIP_Z_INDEX,
2317
+ backgroundColor: 'var(--shk-tooltip-bg, #27272a)',
2318
+ color: 'var(--shk-tooltip-text, #fff)',
2319
+ padding: '4px 8px',
2320
+ borderRadius: '6px',
2321
+ fontSize: '12px',
2322
+ lineHeight: '1.4',
2323
+ whiteSpace: 'normal',
2324
+ maxWidth: 'min(80vw, 22rem)',
2325
+ boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
2326
+ pointerEvents: 'none',
2327
+ opacity: '0',
2328
+ transition: 'opacity 0.15s ease',
2329
+ });
2330
+ this.document.body.appendChild(el);
2331
+ this.tooltipEl = el;
2332
+ const place = () => {
2333
+ const rect = trigger.getBoundingClientRect();
2334
+ const w = el.offsetWidth;
2335
+ const h = el.offsetHeight;
2336
+ const { left, top } = this.computePosition(rect, w, h);
2337
+ el.style.left = `${left}px`;
2338
+ el.style.top = `${top}px`;
2339
+ };
2340
+ place();
2341
+ requestAnimationFrame(() => {
2342
+ if (this.tooltipEl === el) {
2343
+ el.style.opacity = '1';
2344
+ }
2345
+ });
2346
+ const onScrollOrResize = () => place();
2347
+ window.addEventListener('scroll', onScrollOrResize, true);
2348
+ window.addEventListener('resize', onScrollOrResize);
2349
+ this.cleanup = () => {
2350
+ window.removeEventListener('scroll', onScrollOrResize, true);
2351
+ window.removeEventListener('resize', onScrollOrResize);
2352
+ };
2353
+ }
2354
+ computePosition(rect, width, height) {
2355
+ const off = this.offset();
2356
+ const viewportW = this.document.defaultView?.innerWidth ?? 0;
2357
+ const viewportH = this.document.defaultView?.innerHeight ?? 0;
2358
+ let x;
2359
+ let y;
2360
+ if (this.position() === 'right') {
2361
+ x = rect.right + off;
2362
+ y = rect.top + rect.height / 2 - height / 2;
2363
+ }
2364
+ else if (this.position() === 'bottom') {
2365
+ x = rect.left + rect.width / 2 - width / 2;
2366
+ y = rect.bottom + off;
2367
+ }
2368
+ else {
2369
+ x = rect.left + rect.width / 2 - width / 2;
2370
+ y = rect.top - off - height;
2371
+ }
2372
+ const margin = 4;
2373
+ const left = Math.max(margin, Math.min(x, Math.max(margin, viewportW - width - margin)));
2374
+ const top = Math.max(margin, Math.min(y, Math.max(margin, viewportH - height - margin)));
2375
+ return { left, top };
2376
+ }
2377
+ hide() {
2378
+ if (!this.tooltipEl)
2379
+ return;
2380
+ const el = this.tooltipEl;
2381
+ el.style.opacity = '0';
2382
+ this.tooltipEl = null;
2383
+ setTimeout(() => {
2384
+ el.remove();
2385
+ }, 150);
2386
+ }
2387
+ removeTooltip() {
2388
+ this.cleanup?.();
2389
+ this.cleanup = null;
2390
+ this.tooltipEl?.remove();
2391
+ this.tooltipEl = null;
2392
+ }
2393
+ ngOnDestroy() {
2394
+ this.removeTooltip();
2395
+ }
2396
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2397
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.18", type: TooltipComponent, isStandalone: true, selector: "shk-tooltip", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()" } }, ngImport: i0, template: `
2398
+ <div class="shk-tooltip-trigger inline-flex">
2399
+ <ng-content></ng-content>
2400
+ </div>
2401
+ `, isInline: true }); }
2402
+ }
2403
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TooltipComponent, decorators: [{
2404
+ type: Component,
2405
+ args: [{
2406
+ selector: 'shk-tooltip',
2407
+ standalone: true,
2408
+ template: `
2409
+ <div class="shk-tooltip-trigger inline-flex">
2410
+ <ng-content></ng-content>
2411
+ </div>
2412
+ `,
2413
+ }]
2414
+ }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], onMouseEnter: [{
2415
+ type: HostListener,
2416
+ args: ['mouseenter']
2417
+ }], onMouseLeave: [{
2418
+ type: HostListener,
2419
+ args: ['mouseleave']
2420
+ }] } });
2421
+
2152
2422
  const icons$3 = {
2153
2423
  'chevrons-left': LucideChevronsLeft,
2154
2424
  'chevrons-right': LucideChevronsRight,
@@ -2230,12 +2500,32 @@ function getIcon$5(name) {
2230
2500
  return icons$3[name] ?? LucidePlus;
2231
2501
  }
2232
2502
  class TableComponent {
2503
+ getColumnWidth(col) {
2504
+ return col.width || this.DEFAULT_COLUMN_WIDTH;
2505
+ }
2233
2506
  constructor() {
2234
2507
  this.fb = inject(FormBuilder);
2235
2508
  this.cdr = inject(ChangeDetectorRef);
2236
2509
  this.transloco = inject(TranslocoService);
2510
+ this.destroyRef = inject(DestroyRef);
2237
2511
  this.getIcon = getIcon$5;
2238
- this.actionsHeaderLabel = toSignal(this.transloco.selectTranslate('common.actions'), { initialValue: 'Acciones' });
2512
+ /**
2513
+ * FIX responsive: ancho mínimo que toma cada columna (excepto 'actions',
2514
+ * que ya tiene su propio ancho calculado) SOLO en pantallas angostas
2515
+ * (ver el media query en `styles`). En pantallas anchas esta variable no
2516
+ * se usa: las columnas se estiran normalmente al 100% del contenedor,
2517
+ * igual que antes. Ajustable por columna vía `col.width` si alguna
2518
+ * necesita más o menos espacio en mobile.
2519
+ */
2520
+ this.DEFAULT_COLUMN_WIDTH = '140px';
2521
+ this.actionsHeaderLabel = toSignal(this.transloco.selectTranslate('common.actions'), {
2522
+ initialValue: 'Acciones',
2523
+ });
2524
+ // FIX #14: idioma como señal para que los textos traducidos de forma
2525
+ // imperativa (transloco.translate) se recalculen al cambiar de idioma.
2526
+ this.currentLang = toSignal(this.transloco.langChanges$, {
2527
+ initialValue: this.transloco.getActiveLang(),
2528
+ });
2239
2529
  this.data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
2240
2530
  this.columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
2241
2531
  this.rowsPerPage = input(10, ...(ngDevMode ? [{ debugName: "rowsPerPage" }] : /* istanbul ignore next */ []));
@@ -2273,18 +2563,21 @@ class TableComponent {
2273
2563
  this.rowsPerPageLocal = signal(10, ...(ngDevMode ? [{ debugName: "rowsPerPageLocal" }] : /* istanbul ignore next */ []));
2274
2564
  this.totalPages = computed(() => Math.max(1, Math.ceil(this.effectiveTotalRecords() / this.rowsPerPageLocal())), ...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
2275
2565
  this.currentPage = computed(() => Math.floor(this.first() / this.rowsPerPageLocal()) + 1, ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
2566
+ // FIX #4 (medio): debounce también para el buscador global en modo servidor.
2276
2567
  this.filterSubject = new Subject();
2568
+ this.searchSubject = new Subject();
2569
+ // FIX #3/#crítico: para evitar reconstruir el FormGroup (y perder lo escrito
2570
+ // por el usuario) cuando `columns()` cambia de referencia pero el set de
2571
+ // campos filtrables es el mismo.
2572
+ this.lastFilterFieldsSignature = '';
2573
+ this.columnFilterSubscriptions = [];
2277
2574
  this.columnsWithActions = computed(() => {
2278
2575
  const columnsArray = this.columns();
2279
2576
  const showAction = this.showActionRow();
2280
2577
  const rowActionsArray = this.rowActions();
2281
2578
  const translatedHeader = this.actionsHeaderLabel();
2282
2579
  if (showAction && rowActionsArray.length > 0) {
2283
- const actionsWidth = rowActionsArray.length <= 2
2284
- ? '110px'
2285
- : rowActionsArray.length <= 3
2286
- ? '130px'
2287
- : '170px';
2580
+ const actionsWidth = rowActionsArray.length <= 2 ? '110px' : rowActionsArray.length <= 3 ? '130px' : '170px';
2288
2581
  return [
2289
2582
  {
2290
2583
  field: 'actions',
@@ -2303,25 +2596,35 @@ class TableComponent {
2303
2596
  this.effectiveDisplayedData = computed(() => {
2304
2597
  return this.serverSide() ? this.data() : this.displayedData();
2305
2598
  }, ...(ngDevMode ? [{ debugName: "effectiveDisplayedData" }] : /* istanbul ignore next */ []));
2306
- this.activeFilters = computed(() => {
2307
- const form = this.columnFiltersForm;
2308
- if (!form)
2309
- return {};
2310
- const active = {};
2311
- Object.keys(form.controls).forEach((field) => {
2312
- const value = form.get(field)?.value;
2313
- if (value !== null && value !== undefined && value !== '') {
2314
- active[field] = value;
2315
- }
2316
- });
2317
- return active;
2318
- }, ...(ngDevMode ? [{ debugName: "activeFilters" }] : /* istanbul ignore next */ []));
2599
+ // FIX #14: pageReport ahora es un computed reactivo (incluye idioma como
2600
+ // dependencia) en vez de un método imperativo llamado desde el template.
2601
+ this.pageReport = computed(() => {
2602
+ this.currentLang(); // fuerza recálculo al cambiar de idioma
2603
+ const total = this.effectiveTotalRecords();
2604
+ if (total === 0) {
2605
+ return this.transloco.translate('table.pageReport', {
2606
+ from: 0,
2607
+ to: 0,
2608
+ total: 0,
2609
+ });
2610
+ }
2611
+ const from = this.first() + 1;
2612
+ const to = Math.min(this.first() + this.rowsPerPageLocal(), total);
2613
+ return this.transloco.translate('table.pageReport', { from, to, total });
2614
+ }, ...(ngDevMode ? [{ debugName: "pageReport" }] : /* istanbul ignore next */ []));
2319
2615
  this.columnFiltersForm = this.fb.group({});
2320
- this.filterSubject.pipe(debounceTime(300)).subscribe(() => {
2616
+ this.filterSubject
2617
+ .pipe(debounceTime(300), takeUntilDestroyed(this.destroyRef))
2618
+ .subscribe(() => {
2321
2619
  if (this.serverSide()) {
2322
2620
  this.emitFilterChange();
2323
2621
  }
2324
2622
  });
2623
+ this.searchSubject
2624
+ .pipe(debounceTime(300), takeUntilDestroyed(this.destroyRef))
2625
+ .subscribe((value) => {
2626
+ this.searchChange.emit(value);
2627
+ });
2325
2628
  effect(() => {
2326
2629
  const newData = this.data();
2327
2630
  untracked(() => {
@@ -2351,14 +2654,6 @@ class TableComponent {
2351
2654
  });
2352
2655
  }
2353
2656
  });
2354
- effect(() => {
2355
- const activeFilters = this.activeFilters();
2356
- untracked(() => {
2357
- if (!this.serverSide()) {
2358
- this.applyFilters();
2359
- }
2360
- });
2361
- });
2362
2657
  effect(() => {
2363
2658
  const query = this.searchQuery();
2364
2659
  untracked(() => {
@@ -2371,19 +2666,50 @@ class TableComponent {
2371
2666
  this.rowsPerPageLocal.set(this.rowsPerPage());
2372
2667
  });
2373
2668
  }
2669
+ /**
2670
+ * FIX crítico: antes se reconstruía el FormGroup completo (perdiendo el
2671
+ * texto escrito por el usuario en los filtros y el estado de visibilidad
2672
+ * de cada filtro) cada vez que `columns()` cambiaba de referencia, aunque
2673
+ * el conjunto de campos filtrables fuera idéntico. Ahora comparamos una
2674
+ * "firma" del set de campos+tipo de filtro y solo reconstruimos si
2675
+ * realmente cambió. Además, las suscripciones previas se limpian antes de
2676
+ * crear las nuevas para no acumular fugas de memoria.
2677
+ */
2374
2678
  setupColumnFilters(cols) {
2679
+ const filterableCols = cols.filter((c) => c.filter);
2680
+ const signature = filterableCols.map((c) => `${c.field}:${c.filterType ?? 'text'}`).join('|');
2681
+ if (signature === this.lastFilterFieldsSignature && this.columnFiltersForm) {
2682
+ return;
2683
+ }
2684
+ this.lastFilterFieldsSignature = signature;
2685
+ // Limpiar suscripciones anteriores antes de reconstruir.
2686
+ this.columnFilterSubscriptions.forEach((sub) => sub.unsubscribe());
2687
+ this.columnFilterSubscriptions = [];
2688
+ const previousValues = {};
2689
+ if (this.columnFiltersForm) {
2690
+ Object.keys(this.columnFiltersForm.controls).forEach((field) => {
2691
+ previousValues[field] = this.columnFiltersForm.get(field)?.value;
2692
+ });
2693
+ }
2375
2694
  const filterControls = {};
2376
- const filterVisibility = {};
2377
- cols.forEach((col) => {
2378
- if (col.filter) {
2379
- filterControls[col.field] = [col.filterType === 'select' ? null : ''];
2695
+ const filterVisibility = { ...this.showFilterInput() };
2696
+ filterableCols.forEach((col) => {
2697
+ const defaultValue = col.filterType === 'select' ? null : '';
2698
+ const preserved = previousValues.hasOwnProperty(col.field)
2699
+ ? previousValues[col.field]
2700
+ : defaultValue;
2701
+ filterControls[col.field] = [preserved];
2702
+ if (!(col.field in filterVisibility)) {
2380
2703
  filterVisibility[col.field] = false;
2381
2704
  }
2382
2705
  });
2383
2706
  this.columnFiltersForm = this.fb.group(filterControls);
2384
2707
  this.showFilterInput.set(filterVisibility);
2385
2708
  Object.keys(this.columnFiltersForm.controls).forEach((field) => {
2386
- this.columnFiltersForm.get(field)?.valueChanges.subscribe(() => {
2709
+ const sub = this.columnFiltersForm
2710
+ .get(field)
2711
+ ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
2712
+ .subscribe(() => {
2387
2713
  this.first.set(0);
2388
2714
  if (this.serverSide()) {
2389
2715
  this.filterSubject.next();
@@ -2393,6 +2719,9 @@ class TableComponent {
2393
2719
  }
2394
2720
  this.cdr.markForCheck();
2395
2721
  });
2722
+ if (sub) {
2723
+ this.columnFilterSubscriptions.push(sub);
2724
+ }
2396
2725
  });
2397
2726
  }
2398
2727
  emitFilterChange() {
@@ -2476,6 +2805,13 @@ class TableComponent {
2476
2805
  this.updateDisplayedData();
2477
2806
  }
2478
2807
  }
2808
+ /**
2809
+ * FIX: se documenta explícitamente el criterio de valores null/undefined.
2810
+ * Se mantienen SIEMPRE al final de la lista, sin importar la dirección del
2811
+ * orden (comportamiento común e intencional en tablas de datos). Si en tu
2812
+ * caso de uso prefieres que "sigan" la dirección del sort, multiplica
2813
+ * ambos returns por `order`.
2814
+ */
2479
2815
  applySort() {
2480
2816
  const field = this.sortField();
2481
2817
  const order = this.sortOrder();
@@ -2485,8 +2821,10 @@ class TableComponent {
2485
2821
  data.sort((a, b) => {
2486
2822
  const va = a[field];
2487
2823
  const vb = b[field];
2824
+ if (va == null && vb == null)
2825
+ return 0;
2488
2826
  if (va == null)
2489
- return 1;
2827
+ return 1; // nulls siempre al final
2490
2828
  if (vb == null)
2491
2829
  return -1;
2492
2830
  if (va < vb)
@@ -2501,7 +2839,7 @@ class TableComponent {
2501
2839
  this.searchQuery.set(value);
2502
2840
  this.first.set(0);
2503
2841
  if (this.serverSide()) {
2504
- this.searchChange.emit(value);
2842
+ this.searchSubject.next(value);
2505
2843
  }
2506
2844
  }
2507
2845
  clearFilter(field) {
@@ -2517,17 +2855,18 @@ class TableComponent {
2517
2855
  this.showFilterInput.set({ ...visibility, [field]: !visibility[field] });
2518
2856
  this.cdr.markForCheck();
2519
2857
  }
2858
+ /**
2859
+ * FIX #5 (falsy zero): antes el template usaba `columnFiltersForm.get(field)?.value`
2860
+ * directamente en un `@if`/`[class]`, lo cual falla cuando el valor es `0`
2861
+ * (número), ya que `0` es falsy en JS. Este helper compara explícitamente
2862
+ * contra null/undefined/''.
2863
+ */
2864
+ hasFilterValue(field) {
2865
+ const value = this.columnFiltersForm.get(field)?.value;
2866
+ return value !== null && value !== undefined && value !== '';
2867
+ }
2520
2868
  getPageReport() {
2521
- const total = this.effectiveTotalRecords();
2522
- if (total === 0)
2523
- return this.transloco.translate('table.pageReport', {
2524
- from: 0,
2525
- to: 0,
2526
- total: 0,
2527
- });
2528
- const from = this.first() + 1;
2529
- const to = Math.min(this.first() + this.rowsPerPageLocal(), total);
2530
- return this.transloco.translate('table.pageReport', { from, to, total });
2869
+ return this.pageReport();
2531
2870
  }
2532
2871
  goToFirst() {
2533
2872
  const rows = this.rowsPerPageLocal();
@@ -2545,9 +2884,7 @@ class TableComponent {
2545
2884
  }
2546
2885
  }
2547
2886
  goToLast() {
2548
- const total = this.serverSide()
2549
- ? this.totalRecords()
2550
- : this.filteredData().length;
2887
+ const total = this.serverSide() ? this.totalRecords() : this.filteredData().length;
2551
2888
  const rows = this.rowsPerPageLocal();
2552
2889
  const lastFirst = Math.max(0, Math.ceil(total / rows) * rows - rows);
2553
2890
  this.first.set(lastFirst);
@@ -2591,9 +2928,7 @@ class TableComponent {
2591
2928
  return this.first() + this.rowsPerPageLocal() >= total;
2592
2929
  }
2593
2930
  next() {
2594
- const total = this.serverSide()
2595
- ? this.totalRecords()
2596
- : this.filteredData().length;
2931
+ const total = this.serverSide() ? this.totalRecords() : this.filteredData().length;
2597
2932
  const rows = this.rowsPerPageLocal();
2598
2933
  let first = this.first() + rows;
2599
2934
  if (first >= total) {
@@ -2616,9 +2951,7 @@ class TableComponent {
2616
2951
  prev() {
2617
2952
  const rows = this.rowsPerPageLocal();
2618
2953
  let first = this.first() - rows;
2619
- const total = this.serverSide()
2620
- ? this.totalRecords()
2621
- : this.filteredData().length;
2954
+ const total = this.serverSide() ? this.totalRecords() : this.filteredData().length;
2622
2955
  if (first < 0) {
2623
2956
  first = 0;
2624
2957
  }
@@ -2640,21 +2973,15 @@ class TableComponent {
2640
2973
  this.goToFirst();
2641
2974
  }
2642
2975
  getRowActionLabel(action, rowData) {
2643
- return typeof action.label === 'function'
2644
- ? action.label(rowData)
2645
- : action.label;
2976
+ return typeof action.label === 'function' ? action.label(rowData) : action.label;
2646
2977
  }
2647
2978
  getRowActionIconName(action, rowData) {
2648
- return typeof action.icon === 'function'
2649
- ? action.icon(rowData)
2650
- : action.icon;
2979
+ return typeof action.icon === 'function' ? action.icon(rowData) : action.icon;
2651
2980
  }
2652
2981
  getRowActionClass(action, rowData) {
2653
2982
  if (!action.class)
2654
2983
  return '';
2655
- return typeof action.class === 'function'
2656
- ? action.class(rowData)
2657
- : action.class;
2984
+ return typeof action.class === 'function' ? action.class(rowData) : action.class;
2658
2985
  }
2659
2986
  isHeaderActionDisabled(action) {
2660
2987
  return action.isDisabled ? action.isDisabled() : false;
@@ -2674,22 +3001,54 @@ class TableComponent {
2674
3001
  const str = String(value);
2675
3002
  return str.length > limit ? str.slice(0, limit) + '\u2026' : str;
2676
3003
  }
2677
- getTagClass(severity) {
2678
- switch (severity) {
2679
- case 'success':
2680
- return '_shk-bg-green-soft _shk-text-green-600';
2681
- case 'danger':
2682
- return '_shk-bg-red-soft _shk-text-red-600';
2683
- case 'warn':
2684
- return '_shk-bg-yellow-soft _shk-text-yellow-600';
2685
- case 'info':
2686
- return '_shk-bg-blue-soft _shk-text-blue-600';
2687
- default:
2688
- return '_shk-bg-surface-100 _shk-text-surface-700 dark:[background-color:var(--shk-surface-700)] dark:[color:var(--shk-surface-300)]';
3004
+ getTagValue(col, rowData) {
3005
+ return col.tagValue ? col.tagValue(rowData) : String(rowData[col.field] ?? '');
3006
+ }
3007
+ getTagSeverity(col, rowData) {
3008
+ const raw = col.tagSeverity ? col.tagSeverity(rowData) : 'secondary';
3009
+ const allowed = ['secondary', 'success', 'info', 'warning', 'danger', 'contrast'];
3010
+ return allowed.includes(raw) ? raw : 'secondary';
3011
+ }
3012
+ getTagRounded(col, rowData) {
3013
+ if (typeof col.tagRounded === 'function')
3014
+ return col.tagRounded(rowData);
3015
+ return col.tagRounded ?? false;
3016
+ }
3017
+ getTagIcon(col, rowData) {
3018
+ if (typeof col.tagIcon === 'function')
3019
+ return col.tagIcon(rowData);
3020
+ return col.tagIcon ?? '';
3021
+ }
3022
+ getTagStyle(col, rowData) {
3023
+ return col.tagStyle ? col.tagStyle(rowData) || {} : {};
3024
+ }
3025
+ /**
3026
+ * FIX #7 (medio): trackBy real por identidad de fila en vez de `$index`.
3027
+ * Si tu dato no tiene `id`/`_id`, cae de vuelta al índice (comportamiento
3028
+ * anterior), pero se recomienda pasar filas con un identificador único.
3029
+ */
3030
+ trackByRow(row, index) {
3031
+ return row?.id ?? row?._id ?? index;
3032
+ }
3033
+ /** FIX accesibilidad: soporte de teclado (Enter/Espacio) para ordenar columnas. */
3034
+ onHeaderKeydown(event, col) {
3035
+ if (col.field === 'actions' || !col.sortable)
3036
+ return;
3037
+ if (event.key === 'Enter' || event.key === ' ') {
3038
+ event.preventDefault();
3039
+ this.toggleSort(col.field);
2689
3040
  }
2690
3041
  }
3042
+ /** FIX accesibilidad: valor aria-sort para el <th> correspondiente. */
3043
+ getAriaSort(col) {
3044
+ if (col.field === 'actions' || !col.sortable)
3045
+ return 'none';
3046
+ if (this.sortField() !== col.field)
3047
+ return 'none';
3048
+ return this.sortOrder() === 1 ? 'ascending' : 'descending';
3049
+ }
2691
3050
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2692
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: TableComponent, isStandalone: true, selector: "shk-table", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPage: { classPropertyName: "rowsPerPage", publicName: "rowsPerPage", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPageOptions: { classPropertyName: "rowsPerPageOptions", publicName: "rowsPerPageOptions", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, showActionRow: { classPropertyName: "showActionRow", publicName: "showActionRow", isSignal: true, isRequired: false, transformFunction: null }, customTemplates: { classPropertyName: "customTemplates", publicName: "customTemplates", isSignal: true, isRequired: false, transformFunction: null }, headerActions: { classPropertyName: "headerActions", publicName: "headerActions", isSignal: true, isRequired: false, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, hasShadow: { classPropertyName: "hasShadow", publicName: "hasShadow", isSignal: true, isRequired: false, transformFunction: null }, defaultSortField: { classPropertyName: "defaultSortField", publicName: "defaultSortField", isSignal: true, isRequired: false, transformFunction: null }, defaultSortOrder: { classPropertyName: "defaultSortOrder", publicName: "defaultSortOrder", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, serverSide: { classPropertyName: "serverSide", publicName: "serverSide", isSignal: true, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, activeFilter: { classPropertyName: "activeFilter", publicName: "activeFilter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { refresh: "refresh", pageChange: "pageChange", filterChange: "filterChange", searchChange: "searchChange", filterClick: "filterClick" }, ngImport: i0, template: "<div class=\"space-y-2 min-[1440px]:space-y-3 mt-2 min-[1440px]:mt-3 animate-fade-in pb-8\">\n\n @if (showSearch() || filters().length > 0 || headerActions().length > 0) {\n <div class=\"card p-2 min-[1440px]:p-2.5 flex flex-row items-center justify-between gap-2\">\n\n <div class=\"flex items-center gap-2 min-[1440px]:gap-3 flex-1 min-w-0\">\n @if (showSearch()) {\n <div class=\"relative w-full md:w-52 min-[1440px]:w-56\">\n <div class=\"absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none\">\n <svg [lucideIcon]=\"getIcon('search')\" class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n </div>\n <input\n type=\"text\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($any($event.target).value)\"\n [placeholder]=\"searchPlaceholder() || ('common.search_placeholder' | transloco)\"\n class=\"block w-full pl-8 pr-3 py-1.5 text-xs border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md leading-5\n _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\n _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\n focus:outline-none focus:bg-white dark:focus:[background-color:var(--shk-surface-800)] focus:[border-color:var(--shk-primary-500)] focus:ring-1\n focus:[--tw-ring-color:var(--shk-primary-500)]\"\n />\n </div>\n }\n\n @if (filters().length > 0) {\n <div class=\"flex _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] p-0.5 rounded-md ml-auto\">\n @for (f of filters(); track f.value) {\n <button\n type=\"button\"\n (click)=\"onFilterClick(f.value)\"\n class=\"px-2.5 min-[1440px]:px-3 py-1 text-xs font-medium rounded _shk-text-surface-700 dark:[color:var(--shk-surface-700)] transition-all\"\n [class]=\"f.value === activeFilter() ? 'bg-white shadow-sm dark:[background-color:var(--shk-surface-700)]' : ''\"\n >\n {{ f.label }}\n </button>\n }\n </div>\n }\n </div>\n\n @if (headerActions().length > 0) {\n <div class=\"flex items-center gap-1.5 shrink-0\">\n @for (action of headerActions(); track $index) {\n @if (isHeaderActionVisible(action)) {\n <button\n type=\"button\"\n [class]=\"action.class || 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed'\"\n [disabled]=\"isHeaderActionDisabled(action)\"\n (click)=\"action.onClick()\"\n >\n @if (action.icon) {\n <svg\n [lucideIcon]=\"getIcon(action.icon)\"\n class=\"w-3.5 h-3.5\"\n ></svg>\n }\n {{ action.label }}\n </button>\n }\n }\n </div>\n }\n\n </div>\n }\n\n <div\n class=\"card p-0 overflow-hidden\"\n [class.shadow-lg]=\"hasShadow()\"\n >\n\n <div class=\"table-scroll-wrapper _shk-scrollbar\">\n <table class=\"w-full\">\n <thead>\n <tr>\n @for (col of columnsWithActions(); track col.field) {\n <th\n (click)=\"col.field !== 'actions' && col.sortable && toggleSort(col.field)\"\n [style.cursor]=\"col.field !== 'actions' && col.sortable ? 'pointer' : 'default'\"\n [style.width]=\"col.width || 'auto'\"\n [style.min-width]=\"col.field === 'actions' ? '130px' : 'auto'\"\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-left text-[10px] min-[1440px]:text-xs font-bold _shk-text-surface-500 dark:[color:var(--shk-surface-400)] uppercase tracking-wider\n _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] border-b _shk-border dark:[border-color:var(--shk-surface-200)]\"\n >\n @if (col.field !== 'actions') {\n <div class=\"flex items-center justify-between gap-2\">\n <span>{{ col.header }}</span>\n <div class=\"flex items-center gap-1\">\n @if (col.sortable) {\n <div class=\"relative group\">\n @if (sortField() === col.field) {\n <svg [lucideIcon]=\"getIcon(sortOrder() === 1 ? 'arrow-up' : 'arrow-down')\"\n class=\"w-3.5 h-3.5 _shk-text-primary\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('arrow-up-down')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n }\n <span class=\"absolute top-full left-1/2 -translate-x-1/2 mt-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ 'table.tooltips.sort' | transloco }}\n </span>\n </div>\n }\n @if (col.filter) {\n <div class=\"relative group\">\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleFilter(col.field)\"\n class=\"_shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] transition-colors\"\n >\n <svg\n [lucideIcon]=\"getIcon('filter')\"\n class=\"w-3.5 h-3.5\"\n [class._shk-text-primary]=\"columnFiltersForm.get(col.field)?.value\"\n ></svg>\n <span class=\"absolute top-full left-1/2 -translate-x-1/2 mt-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ 'table.tooltips.filter' | transloco }}\n </span>\n </button>\n </div>\n }\n </div>\n </div>\n\n @if (col.filter && showFilterInput()[col.field]) {\n <div\n class=\"mt-2\"\n [formGroup]=\"columnFiltersForm\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"relative\">\n @if (col.filterType !== 'select') {\n <input\n type=\"text\"\n [formControlName]=\"col.field\"\n class=\"w-full px-3 py-1.5 text-sm rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\n focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)]\n placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\"\n [placeholder]=\"col.filterPlaceholder || ('common.search_placeholder' | transloco)\"\n />\n }\n @if (col.filterType === 'select') {\n <select\n [formControlName]=\"col.field\"\n class=\"w-full px-2 py-1 text-xs rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] appearance-none\"\n >\n <option [ngValue]=\"null\">A/D</option>\n @for (opt of col.filterOptions || []; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @if (columnFiltersForm.get(col.field)?.value && col.filterType !== 'select') {\n <button\n type=\"button\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)]\"\n (click)=\"clearFilter(col.field)\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <div class=\"flex items-center justify-center\">\n <span>{{ col.header }}</span>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n\n @if (loading()) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex items-center justify-center\">\n <svg [lucideIcon]=\"getIcon('loader')\" class=\"w-6 h-6 animate-spin _shk-text-primary\"></svg>\n </div>\n </td>\n </tr>\n </tbody>\n } @else if (effectiveDisplayedData().length === 0) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex flex-col items-center\">\n <div class=\"w-16 h-16 _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] rounded-full flex items-center justify-center mb-4\">\n <svg [lucideIcon]=\"getIcon('inbox')\" class=\"w-8 h-8 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n </div>\n <p class=\"text-lg font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\">\n {{ emptyMessage() || ('common.no_records' | transloco) }}\n </p>\n </div>\n </td>\n </tr>\n </tbody>\n } @else {\n <tbody>\n @for (rowData of effectiveDisplayedData(); track $index) {\n <tr class=\"hover:[background-color:color-mix(in srgb,var(--shk-primary-50)30%,transparent)] dark:hover:[background-color:color-mix(in srgb,var(--shk-primary-900)10%,transparent)] transition-colors duration-200 border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n @for (col of columnsWithActions(); track col.field) {\n <td\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)] whitespace-nowrap\"\n [style.overflow]=\"col.field === 'actions' ? 'visible' : 'hidden'\"\n [style.text-overflow]=\"col.field === 'actions' ? 'clip' : 'ellipsis'\"\n >\n @if (col.field !== 'actions') {\n @if (col.template === 'tag') {\n <span\n class=\"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium\"\n [class]=\"getTagClass(col.tagSeverity ? col.tagSeverity(rowData) : undefined)\"\n [style]=\"col.tagStyle ? col.tagStyle(rowData) : undefined\"\n >\n {{ col.tagValue ? col.tagValue(rowData) : rowData[col.field] }}\n </span>\n } @else if (col.format) {\n <span>{{ col.format(rowData) }}</span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n customTemplates()[col.field] || defaultTemplate;\n context: { $implicit: rowData, field: col.field }\n \"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-start gap-px\">\n @for (action of rowActions(); track $index) {\n @if (isRowActionVisible(action, rowData)) {\n <div class=\"relative group\">\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData)\"\n [class]=\"'table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md ' + getRowActionClass(action, rowData)\"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n <span class=\"absolute bottom-full left-1/2 -translate-x-1/2 mb-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ getRowActionLabel(action, rowData) }}\n </span>\n </div>\n }\n }\n </div>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n }\n </table>\n\n <ng-template #defaultTemplate let-rowData let-field=\"field\">\n {{ truncate(rowData[field], 30) }}\n </ng-template>\n </div>\n\n <div class=\"flex items-center justify-between gap-4 px-2 min-[1440px]:px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <div class=\"text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">{{ getPageReport() }}</div>\n <div class=\"flex items-center gap-0.5\">\n <button\n type=\"button\"\n (click)=\"goToFirst()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span class=\"px-2 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\">{{ currentPage() }} / {{ totalPages() }}</span>\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"goToLast()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n <div class=\"flex items-center gap-1.5 text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">\n <span>{{ 'table.rows' | transloco }}</span>\n <select\n [value]=\"rowsPerPageLocal()\"\n (change)=\"onRowsPerPageChange($any($event.target).value)\"\n class=\"border _shk-border dark:[border-color:var(--shk-surface-200)] rounded px-2 py-1 text-xs bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:[border-color:var(--shk-primary-500)]\"\n >\n @for (opt of rowsPerPageOptions(); track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n\n</div>\n", styles: ["input:focus{outline:none}input[type=text]{font-weight:400!important}table th{white-space:nowrap}table td{white-space:nowrap;text-overflow:ellipsis}td button svg{width:1.125rem!important;height:1.125rem!important}td button:disabled{opacity:.4;cursor:not-allowed}.table-action-btn svg{width:1rem!important;height:1rem!important;min-width:1rem!important;min-height:1rem!important}.table-action-btn{padding:.5rem!important;min-width:2rem!important;min-height:2rem!important;display:inline-flex!important;align-items:center!important;justify-content:center!important}.table-scroll-wrapper{width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-scroll-wrapper table{table-layout:fixed;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.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: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }, { kind: "pipe", type: i1$1.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3051
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: TableComponent, isStandalone: true, selector: "shk-table", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPage: { classPropertyName: "rowsPerPage", publicName: "rowsPerPage", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPageOptions: { classPropertyName: "rowsPerPageOptions", publicName: "rowsPerPageOptions", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, showActionRow: { classPropertyName: "showActionRow", publicName: "showActionRow", isSignal: true, isRequired: false, transformFunction: null }, customTemplates: { classPropertyName: "customTemplates", publicName: "customTemplates", isSignal: true, isRequired: false, transformFunction: null }, headerActions: { classPropertyName: "headerActions", publicName: "headerActions", isSignal: true, isRequired: false, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, hasShadow: { classPropertyName: "hasShadow", publicName: "hasShadow", isSignal: true, isRequired: false, transformFunction: null }, defaultSortField: { classPropertyName: "defaultSortField", publicName: "defaultSortField", isSignal: true, isRequired: false, transformFunction: null }, defaultSortOrder: { classPropertyName: "defaultSortOrder", publicName: "defaultSortOrder", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, serverSide: { classPropertyName: "serverSide", publicName: "serverSide", isSignal: true, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, activeFilter: { classPropertyName: "activeFilter", publicName: "activeFilter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { refresh: "refresh", pageChange: "pageChange", filterChange: "filterChange", searchChange: "searchChange", filterClick: "filterClick" }, ngImport: i0, template: "<div class=\"space-y-2 min-[1440px]:space-y-3 mt-2 min-[1440px]:mt-3 animate-fade-in pb-8\">\n @if (showSearch() || filters().length > 0 || headerActions().length > 0) {\n <div class=\"card p-2 min-[1440px]:p-2.5 flex flex-row items-center justify-between gap-2\">\n <div class=\"flex items-center gap-2 min-[1440px]:gap-3 flex-1 min-w-0\">\n @if (showSearch()) {\n <div class=\"relative w-full md:w-52 min-[1440px]:w-56\">\n <label [for]=\"'shk-table-search'\" class=\"sr-only\">{{\n searchPlaceholder() || ('common.search_placeholder' | transloco)\n }}</label>\n <div class=\"absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none\">\n <svg\n [lucideIcon]=\"getIcon('search')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <input\n id=\"shk-table-search\"\n type=\"text\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($any($event.target).value)\"\n [placeholder]=\"searchPlaceholder() || ('common.search_placeholder' | transloco)\"\n class=\"block w-full pl-8 pr-3 py-1.5 text-xs border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md leading-5 _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:bg-white dark:focus:[background-color:var(--shk-surface-800)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)]\"\n />\n </div>\n }\n @if (filters().length > 0) {\n <div\n class=\"flex _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] p-0.5 rounded-md ml-auto\"\n role=\"group\"\n >\n @for (f of filters(); track f.value) {\n <button\n type=\"button\"\n (click)=\"onFilterClick(f.value)\"\n [attr.aria-pressed]=\"f.value === activeFilter()\"\n class=\"px-2.5 min-[1440px]:px-3 py-1 text-xs font-medium rounded _shk-text-surface-700 dark:[color:var(--shk-surface-700)] transition-all\"\n [class]=\"\n f.value === activeFilter()\n ? 'bg-white shadow-sm dark:[background-color:var(--shk-surface-700)]'\n : ''\n \"\n >\n {{ f.label }}\n </button>\n }\n </div>\n }\n </div>\n @if (headerActions().length > 0) {\n <div class=\"flex items-center gap-1.5 shrink-0\">\n @for (action of headerActions(); track $index) {\n @if (isHeaderActionVisible(action)) {\n <button\n type=\"button\"\n [class]=\"\n 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed ' +\n (action.class || '')\n \"\n [disabled]=\"isHeaderActionDisabled(action)\"\n [attr.aria-label]=\"action.label\"\n (click)=\"action.onClick()\"\n >\n @if (action.icon) {\n <svg [lucideIcon]=\"getIcon(action.icon)\" class=\"w-3.5 h-3.5\"></svg>\n }\n {{ action.label }}\n </button>\n }\n }\n </div>\n }\n </div>\n }\n <div class=\"card p-0 overflow-hidden\" [class.shadow-lg]=\"hasShadow()\">\n <div class=\"table-scroll-wrapper _shk-scrollbar\">\n <table>\n <thead>\n <tr>\n @for (col of columnsWithActions(); track col.field) {\n <th\n scope=\"col\"\n (click)=\"col.field !== 'actions' && col.sortable && toggleSort(col.field)\"\n (keydown)=\"onHeaderKeydown($event, col)\"\n [tabindex]=\"col.field !== 'actions' && col.sortable ? 0 : -1\"\n [attr.role]=\"col.field !== 'actions' && col.sortable ? 'button' : null\"\n [attr.aria-sort]=\"getAriaSort(col)\"\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n [style.cursor]=\"col.field !== 'actions' && col.sortable ? 'pointer' : 'default'\"\n [style.width]=\"col.field === 'actions' ? col.width : null\"\n [style.min-width]=\"col.field === 'actions' ? col.width : null\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-left text-[10px] min-[1440px]:text-xs font-bold _shk-text-surface-500 dark:[color:var(--shk-surface-400)] uppercase tracking-wider _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] border-b _shk-border dark:[border-color:var(--shk-surface-200)] focus:outline-none focus-visible:ring-1 focus-visible:[--tw-ring-color:var(--shk-primary-500)]\"\n >\n @if (col.field !== 'actions') {\n <div class=\"flex items-center justify-between gap-2\">\n <span>{{ col.header }}</span>\n <div class=\"flex items-center gap-1\">\n @if (col.sortable) {\n <shk-tooltip [text]=\"'table.tooltips.sort' | transloco\">\n @if (sortField() === col.field) {\n <svg\n [lucideIcon]=\"getIcon(sortOrder() === 1 ? 'arrow-up' : 'arrow-down')\"\n class=\"w-3.5 h-3.5 _shk-text-primary\"\n ></svg>\n } @else {\n <svg\n [lucideIcon]=\"getIcon('arrow-up-down')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n }\n </shk-tooltip>\n }\n @if (col.filter) {\n <shk-tooltip [text]=\"'table.tooltips.filter' | transloco\">\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleFilter(col.field)\"\n [attr.aria-label]=\"'table.tooltips.filter' | transloco\"\n [attr.aria-expanded]=\"showFilterInput()[col.field] || false\"\n class=\"_shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] transition-colors\"\n >\n <svg\n [lucideIcon]=\"getIcon('filter')\"\n class=\"w-3.5 h-3.5\"\n [class._shk-text-primary]=\"hasFilterValue(col.field)\"\n ></svg>\n </button>\n </shk-tooltip>\n }\n </div>\n </div>\n @if (col.filter && showFilterInput()[col.field]) {\n <div\n class=\"mt-2\"\n [formGroup]=\"columnFiltersForm\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"relative\">\n @if (col.filterType !== 'select') {\n <label [for]=\"'shk-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <input\n [id]=\"'shk-filter-' + col.field\"\n type=\"text\"\n [formControlName]=\"col.field\"\n class=\"w-full px-3 py-1.5 text-sm rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\"\n [placeholder]=\"\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n \"\n />\n }\n @if (col.filterType === 'select') {\n <label [for]=\"'shk-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <select\n [id]=\"'shk-filter-' + col.field\"\n [formControlName]=\"col.field\"\n class=\"w-full px-2 py-1 text-xs rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] appearance-none\"\n >\n <option [ngValue]=\"null\">{{ 'table.filterAll' | transloco }}</option>\n @for (opt of col.filterOptions || []; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @if (hasFilterValue(col.field) && col.filterType !== 'select') {\n <button\n type=\"button\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)]\"\n (click)=\"clearFilter(col.field)\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <div class=\"flex items-center justify-center\">\n <span>{{ col.header }}</span>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n @if (loading()) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div\n class=\"flex items-center justify-center\"\n role=\"status\"\n [attr.aria-label]=\"'common.loading' | transloco\"\n >\n <svg\n [lucideIcon]=\"getIcon('loader')\"\n class=\"w-6 h-6 animate-spin _shk-text-primary\"\n ></svg>\n </div>\n </td>\n </tr>\n </tbody>\n } @else if (effectiveDisplayedData().length === 0) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex flex-col items-center\">\n <div\n class=\"w-16 h-16 _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] rounded-full flex items-center justify-center mb-4\"\n >\n <svg\n [lucideIcon]=\"getIcon('inbox')\"\n class=\"w-8 h-8 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <p\n class=\"text-lg font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\"\n >\n {{ emptyMessage() || ('common.no_records' | transloco) }}\n </p>\n </div>\n </td>\n </tr>\n </tbody>\n } @else {\n <tbody>\n @for (rowData of effectiveDisplayedData(); track trackByRow(rowData, $index)) {\n <tr\n class=\"hover:[background-color:color-mix(in srgb,var(--shk-primary-50)30%,transparent)] dark:hover:[background-color:color-mix(in srgb,var(--shk-primary-900)10%,transparent)] transition-colors duration-200 border-b _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n @for (col of columnsWithActions(); track col.field) {\n <td\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)] whitespace-nowrap\"\n [style.overflow]=\"col.field === 'actions' ? 'visible' : 'hidden'\"\n [style.text-overflow]=\"col.field === 'actions' ? 'clip' : 'ellipsis'\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n >\n @if (col.field !== 'actions') {\n @if (col.template === 'tag') {\n <shk-tag\n [value]=\"getTagValue(col, rowData)\"\n [severity]=\"getTagSeverity(col, rowData)\"\n [rounded]=\"getTagRounded(col, rowData)\"\n [icon]=\"getTagIcon(col, rowData)\"\n [style]=\"getTagStyle(col, rowData)\"\n />\n } @else if (col.format) {\n <span>{{ col.format(rowData) }}</span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n customTemplates()[col.field] || defaultTemplate;\n context: { $implicit: rowData, field: col.field }\n \"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-start gap-px\">\n @for (action of rowActions(); track $index) {\n @if (isRowActionVisible(action, rowData)) {\n <div class=\"relative group\">\n <shk-tooltip\n [text]=\"getRowActionLabel(action, rowData)\"\n position=\"top\"\n >\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData)\"\n [class]=\"\n 'table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md ' +\n getRowActionClass(action, rowData)\n \"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n [attr.aria-label]=\"getRowActionLabel(action, rowData)\"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n </shk-tooltip>\n </div>\n }\n }\n </div>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n }\n </table>\n <ng-template #defaultTemplate let-rowData let-field=\"field\">\n {{ truncate(rowData[field], 30) }}\n </ng-template>\n </div>\n <div\n class=\"flex items-center justify-between gap-4 px-2 min-[1440px]:px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n <div class=\"text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">\n {{ pageReport() }}\n </div>\n <div\n class=\"flex items-center gap-0.5\"\n role=\"navigation\"\n >\n <button\n type=\"button\"\n (click)=\"goToFirst()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span\n class=\"px-2 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\"\n >{{ currentPage() }} / {{ totalPages() }}</span\n >\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"goToLast()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n <div\n class=\"flex items-center gap-1.5 text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\"\n >\n <label [for]=\"'shk-rows-per-page'\">{{ 'table.rows' | transloco }}</label>\n <select\n id=\"shk-rows-per-page\"\n [value]=\"rowsPerPageLocal()\"\n (change)=\"onRowsPerPageChange($any($event.target).value)\"\n class=\"border _shk-border dark:[border-color:var(--shk-surface-200)] rounded px-2 py-1 text-xs bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:[border-color:var(--shk-primary-500)]\"\n >\n @for (opt of rowsPerPageOptions(); track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n</div>\n", styles: ["input:focus{outline:none}input[type=text]{font-weight:400!important}table th,table td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}td button svg{width:1.125rem!important;height:1.125rem!important}td button:disabled{opacity:.4;cursor:not-allowed}.table-action-btn svg{width:1rem!important;height:1rem!important;min-width:1rem!important;min-height:1rem!important}.table-action-btn{padding:.5rem!important;min-width:2rem!important;min-height:2rem!important;display:inline-flex!important;align-items:center!important;justify-content:center!important}.table-scroll-wrapper{width:100%;overflow-x:auto;overflow-y:hidden;border-radius:inherit;-webkit-overflow-scrolling:touch;container-type:inline-size}.table-scroll-wrapper table{table-layout:fixed;width:100%}@container (max-width: 768px){.table-scroll-wrapper{scroll-snap-type:x mandatory}.table-scroll-wrapper table{table-layout:auto;width:100%}.table-scroll-wrapper th:not([data-shk-actions]),.table-scroll-wrapper td:not([data-shk-actions]){min-width:var(--shk-col-width, 140px);max-width:var(--shk-col-width, 140px)}.table-scroll-wrapper th,.table-scroll-wrapper td{scroll-snap-align:start}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.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: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }, { kind: "component", type: TagComponent, selector: "shk-tag", inputs: ["value", "severity", "rounded", "icon", "style", "styleClass"] }, { kind: "component", type: TooltipComponent, selector: "shk-tooltip", inputs: ["text", "position", "mode", "offset"] }, { kind: "pipe", type: i1$1.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2693
3052
  }
2694
3053
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TableComponent, decorators: [{
2695
3054
  type: Component,
@@ -2698,118 +3057,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImpo
2698
3057
  ReactiveFormsModule,
2699
3058
  TranslocoModule,
2700
3059
  LucideDynamicIcon,
2701
- ], template: "<div class=\"space-y-2 min-[1440px]:space-y-3 mt-2 min-[1440px]:mt-3 animate-fade-in pb-8\">\n\n @if (showSearch() || filters().length > 0 || headerActions().length > 0) {\n <div class=\"card p-2 min-[1440px]:p-2.5 flex flex-row items-center justify-between gap-2\">\n\n <div class=\"flex items-center gap-2 min-[1440px]:gap-3 flex-1 min-w-0\">\n @if (showSearch()) {\n <div class=\"relative w-full md:w-52 min-[1440px]:w-56\">\n <div class=\"absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none\">\n <svg [lucideIcon]=\"getIcon('search')\" class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n </div>\n <input\n type=\"text\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($any($event.target).value)\"\n [placeholder]=\"searchPlaceholder() || ('common.search_placeholder' | transloco)\"\n class=\"block w-full pl-8 pr-3 py-1.5 text-xs border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md leading-5\n _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\n _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\n focus:outline-none focus:bg-white dark:focus:[background-color:var(--shk-surface-800)] focus:[border-color:var(--shk-primary-500)] focus:ring-1\n focus:[--tw-ring-color:var(--shk-primary-500)]\"\n />\n </div>\n }\n\n @if (filters().length > 0) {\n <div class=\"flex _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] p-0.5 rounded-md ml-auto\">\n @for (f of filters(); track f.value) {\n <button\n type=\"button\"\n (click)=\"onFilterClick(f.value)\"\n class=\"px-2.5 min-[1440px]:px-3 py-1 text-xs font-medium rounded _shk-text-surface-700 dark:[color:var(--shk-surface-700)] transition-all\"\n [class]=\"f.value === activeFilter() ? 'bg-white shadow-sm dark:[background-color:var(--shk-surface-700)]' : ''\"\n >\n {{ f.label }}\n </button>\n }\n </div>\n }\n </div>\n\n @if (headerActions().length > 0) {\n <div class=\"flex items-center gap-1.5 shrink-0\">\n @for (action of headerActions(); track $index) {\n @if (isHeaderActionVisible(action)) {\n <button\n type=\"button\"\n [class]=\"action.class || 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed'\"\n [disabled]=\"isHeaderActionDisabled(action)\"\n (click)=\"action.onClick()\"\n >\n @if (action.icon) {\n <svg\n [lucideIcon]=\"getIcon(action.icon)\"\n class=\"w-3.5 h-3.5\"\n ></svg>\n }\n {{ action.label }}\n </button>\n }\n }\n </div>\n }\n\n </div>\n }\n\n <div\n class=\"card p-0 overflow-hidden\"\n [class.shadow-lg]=\"hasShadow()\"\n >\n\n <div class=\"table-scroll-wrapper _shk-scrollbar\">\n <table class=\"w-full\">\n <thead>\n <tr>\n @for (col of columnsWithActions(); track col.field) {\n <th\n (click)=\"col.field !== 'actions' && col.sortable && toggleSort(col.field)\"\n [style.cursor]=\"col.field !== 'actions' && col.sortable ? 'pointer' : 'default'\"\n [style.width]=\"col.width || 'auto'\"\n [style.min-width]=\"col.field === 'actions' ? '130px' : 'auto'\"\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-left text-[10px] min-[1440px]:text-xs font-bold _shk-text-surface-500 dark:[color:var(--shk-surface-400)] uppercase tracking-wider\n _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] border-b _shk-border dark:[border-color:var(--shk-surface-200)]\"\n >\n @if (col.field !== 'actions') {\n <div class=\"flex items-center justify-between gap-2\">\n <span>{{ col.header }}</span>\n <div class=\"flex items-center gap-1\">\n @if (col.sortable) {\n <div class=\"relative group\">\n @if (sortField() === col.field) {\n <svg [lucideIcon]=\"getIcon(sortOrder() === 1 ? 'arrow-up' : 'arrow-down')\"\n class=\"w-3.5 h-3.5 _shk-text-primary\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('arrow-up-down')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n }\n <span class=\"absolute top-full left-1/2 -translate-x-1/2 mt-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ 'table.tooltips.sort' | transloco }}\n </span>\n </div>\n }\n @if (col.filter) {\n <div class=\"relative group\">\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleFilter(col.field)\"\n class=\"_shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] transition-colors\"\n >\n <svg\n [lucideIcon]=\"getIcon('filter')\"\n class=\"w-3.5 h-3.5\"\n [class._shk-text-primary]=\"columnFiltersForm.get(col.field)?.value\"\n ></svg>\n <span class=\"absolute top-full left-1/2 -translate-x-1/2 mt-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ 'table.tooltips.filter' | transloco }}\n </span>\n </button>\n </div>\n }\n </div>\n </div>\n\n @if (col.filter && showFilterInput()[col.field]) {\n <div\n class=\"mt-2\"\n [formGroup]=\"columnFiltersForm\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"relative\">\n @if (col.filterType !== 'select') {\n <input\n type=\"text\"\n [formControlName]=\"col.field\"\n class=\"w-full px-3 py-1.5 text-sm rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\n focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)]\n placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\"\n [placeholder]=\"col.filterPlaceholder || ('common.search_placeholder' | transloco)\"\n />\n }\n @if (col.filterType === 'select') {\n <select\n [formControlName]=\"col.field\"\n class=\"w-full px-2 py-1 text-xs rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] appearance-none\"\n >\n <option [ngValue]=\"null\">A/D</option>\n @for (opt of col.filterOptions || []; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @if (columnFiltersForm.get(col.field)?.value && col.filterType !== 'select') {\n <button\n type=\"button\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)]\"\n (click)=\"clearFilter(col.field)\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <div class=\"flex items-center justify-center\">\n <span>{{ col.header }}</span>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n\n @if (loading()) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex items-center justify-center\">\n <svg [lucideIcon]=\"getIcon('loader')\" class=\"w-6 h-6 animate-spin _shk-text-primary\"></svg>\n </div>\n </td>\n </tr>\n </tbody>\n } @else if (effectiveDisplayedData().length === 0) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex flex-col items-center\">\n <div class=\"w-16 h-16 _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] rounded-full flex items-center justify-center mb-4\">\n <svg [lucideIcon]=\"getIcon('inbox')\" class=\"w-8 h-8 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n </div>\n <p class=\"text-lg font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\">\n {{ emptyMessage() || ('common.no_records' | transloco) }}\n </p>\n </div>\n </td>\n </tr>\n </tbody>\n } @else {\n <tbody>\n @for (rowData of effectiveDisplayedData(); track $index) {\n <tr class=\"hover:[background-color:color-mix(in srgb,var(--shk-primary-50)30%,transparent)] dark:hover:[background-color:color-mix(in srgb,var(--shk-primary-900)10%,transparent)] transition-colors duration-200 border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n @for (col of columnsWithActions(); track col.field) {\n <td\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)] whitespace-nowrap\"\n [style.overflow]=\"col.field === 'actions' ? 'visible' : 'hidden'\"\n [style.text-overflow]=\"col.field === 'actions' ? 'clip' : 'ellipsis'\"\n >\n @if (col.field !== 'actions') {\n @if (col.template === 'tag') {\n <span\n class=\"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium\"\n [class]=\"getTagClass(col.tagSeverity ? col.tagSeverity(rowData) : undefined)\"\n [style]=\"col.tagStyle ? col.tagStyle(rowData) : undefined\"\n >\n {{ col.tagValue ? col.tagValue(rowData) : rowData[col.field] }}\n </span>\n } @else if (col.format) {\n <span>{{ col.format(rowData) }}</span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n customTemplates()[col.field] || defaultTemplate;\n context: { $implicit: rowData, field: col.field }\n \"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-start gap-px\">\n @for (action of rowActions(); track $index) {\n @if (isRowActionVisible(action, rowData)) {\n <div class=\"relative group\">\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData)\"\n [class]=\"'table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md ' + getRowActionClass(action, rowData)\"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n <span class=\"absolute bottom-full left-1/2 -translate-x-1/2 mb-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ getRowActionLabel(action, rowData) }}\n </span>\n </div>\n }\n }\n </div>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n }\n </table>\n\n <ng-template #defaultTemplate let-rowData let-field=\"field\">\n {{ truncate(rowData[field], 30) }}\n </ng-template>\n </div>\n\n <div class=\"flex items-center justify-between gap-4 px-2 min-[1440px]:px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <div class=\"text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">{{ getPageReport() }}</div>\n <div class=\"flex items-center gap-0.5\">\n <button\n type=\"button\"\n (click)=\"goToFirst()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span class=\"px-2 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\">{{ currentPage() }} / {{ totalPages() }}</span>\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"goToLast()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n <div class=\"flex items-center gap-1.5 text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">\n <span>{{ 'table.rows' | transloco }}</span>\n <select\n [value]=\"rowsPerPageLocal()\"\n (change)=\"onRowsPerPageChange($any($event.target).value)\"\n class=\"border _shk-border dark:[border-color:var(--shk-surface-200)] rounded px-2 py-1 text-xs bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:[border-color:var(--shk-primary-500)]\"\n >\n @for (opt of rowsPerPageOptions(); track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n\n</div>\n", styles: ["input:focus{outline:none}input[type=text]{font-weight:400!important}table th{white-space:nowrap}table td{white-space:nowrap;text-overflow:ellipsis}td button svg{width:1.125rem!important;height:1.125rem!important}td button:disabled{opacity:.4;cursor:not-allowed}.table-action-btn svg{width:1rem!important;height:1rem!important;min-width:1rem!important;min-height:1rem!important}.table-action-btn{padding:.5rem!important;min-width:2rem!important;min-height:2rem!important;display:inline-flex!important;align-items:center!important;justify-content:center!important}.table-scroll-wrapper{width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-scroll-wrapper table{table-layout:fixed;width:100%}\n"] }]
3060
+ TagComponent,
3061
+ TooltipComponent,
3062
+ ], template: "<div class=\"space-y-2 min-[1440px]:space-y-3 mt-2 min-[1440px]:mt-3 animate-fade-in pb-8\">\n @if (showSearch() || filters().length > 0 || headerActions().length > 0) {\n <div class=\"card p-2 min-[1440px]:p-2.5 flex flex-row items-center justify-between gap-2\">\n <div class=\"flex items-center gap-2 min-[1440px]:gap-3 flex-1 min-w-0\">\n @if (showSearch()) {\n <div class=\"relative w-full md:w-52 min-[1440px]:w-56\">\n <label [for]=\"'shk-table-search'\" class=\"sr-only\">{{\n searchPlaceholder() || ('common.search_placeholder' | transloco)\n }}</label>\n <div class=\"absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none\">\n <svg\n [lucideIcon]=\"getIcon('search')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <input\n id=\"shk-table-search\"\n type=\"text\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($any($event.target).value)\"\n [placeholder]=\"searchPlaceholder() || ('common.search_placeholder' | transloco)\"\n class=\"block w-full pl-8 pr-3 py-1.5 text-xs border _shk-border dark:[border-color:var(--shk-surface-200)] rounded-md leading-5 _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:bg-white dark:focus:[background-color:var(--shk-surface-800)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)]\"\n />\n </div>\n }\n @if (filters().length > 0) {\n <div\n class=\"flex _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] p-0.5 rounded-md ml-auto\"\n role=\"group\"\n >\n @for (f of filters(); track f.value) {\n <button\n type=\"button\"\n (click)=\"onFilterClick(f.value)\"\n [attr.aria-pressed]=\"f.value === activeFilter()\"\n class=\"px-2.5 min-[1440px]:px-3 py-1 text-xs font-medium rounded _shk-text-surface-700 dark:[color:var(--shk-surface-700)] transition-all\"\n [class]=\"\n f.value === activeFilter()\n ? 'bg-white shadow-sm dark:[background-color:var(--shk-surface-700)]'\n : ''\n \"\n >\n {{ f.label }}\n </button>\n }\n </div>\n }\n </div>\n @if (headerActions().length > 0) {\n <div class=\"flex items-center gap-1.5 shrink-0\">\n @for (action of headerActions(); track $index) {\n @if (isHeaderActionVisible(action)) {\n <button\n type=\"button\"\n [class]=\"\n 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed ' +\n (action.class || '')\n \"\n [disabled]=\"isHeaderActionDisabled(action)\"\n [attr.aria-label]=\"action.label\"\n (click)=\"action.onClick()\"\n >\n @if (action.icon) {\n <svg [lucideIcon]=\"getIcon(action.icon)\" class=\"w-3.5 h-3.5\"></svg>\n }\n {{ action.label }}\n </button>\n }\n }\n </div>\n }\n </div>\n }\n <div class=\"card p-0 overflow-hidden\" [class.shadow-lg]=\"hasShadow()\">\n <div class=\"table-scroll-wrapper _shk-scrollbar\">\n <table>\n <thead>\n <tr>\n @for (col of columnsWithActions(); track col.field) {\n <th\n scope=\"col\"\n (click)=\"col.field !== 'actions' && col.sortable && toggleSort(col.field)\"\n (keydown)=\"onHeaderKeydown($event, col)\"\n [tabindex]=\"col.field !== 'actions' && col.sortable ? 0 : -1\"\n [attr.role]=\"col.field !== 'actions' && col.sortable ? 'button' : null\"\n [attr.aria-sort]=\"getAriaSort(col)\"\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n [style.cursor]=\"col.field !== 'actions' && col.sortable ? 'pointer' : 'default'\"\n [style.width]=\"col.field === 'actions' ? col.width : null\"\n [style.min-width]=\"col.field === 'actions' ? col.width : null\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-left text-[10px] min-[1440px]:text-xs font-bold _shk-text-surface-500 dark:[color:var(--shk-surface-400)] uppercase tracking-wider _shk-bg-surface-50 dark:[background-color:var(--shk-surface-100)] border-b _shk-border dark:[border-color:var(--shk-surface-200)] focus:outline-none focus-visible:ring-1 focus-visible:[--tw-ring-color:var(--shk-primary-500)]\"\n >\n @if (col.field !== 'actions') {\n <div class=\"flex items-center justify-between gap-2\">\n <span>{{ col.header }}</span>\n <div class=\"flex items-center gap-1\">\n @if (col.sortable) {\n <shk-tooltip [text]=\"'table.tooltips.sort' | transloco\">\n @if (sortField() === col.field) {\n <svg\n [lucideIcon]=\"getIcon(sortOrder() === 1 ? 'arrow-up' : 'arrow-down')\"\n class=\"w-3.5 h-3.5 _shk-text-primary\"\n ></svg>\n } @else {\n <svg\n [lucideIcon]=\"getIcon('arrow-up-down')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n }\n </shk-tooltip>\n }\n @if (col.filter) {\n <shk-tooltip [text]=\"'table.tooltips.filter' | transloco\">\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleFilter(col.field)\"\n [attr.aria-label]=\"'table.tooltips.filter' | transloco\"\n [attr.aria-expanded]=\"showFilterInput()[col.field] || false\"\n class=\"_shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] transition-colors\"\n >\n <svg\n [lucideIcon]=\"getIcon('filter')\"\n class=\"w-3.5 h-3.5\"\n [class._shk-text-primary]=\"hasFilterValue(col.field)\"\n ></svg>\n </button>\n </shk-tooltip>\n }\n </div>\n </div>\n @if (col.filter && showFilterInput()[col.field]) {\n <div\n class=\"mt-2\"\n [formGroup]=\"columnFiltersForm\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"relative\">\n @if (col.filterType !== 'select') {\n <label [for]=\"'shk-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <input\n [id]=\"'shk-filter-' + col.field\"\n type=\"text\"\n [formControlName]=\"col.field\"\n class=\"w-full px-3 py-1.5 text-sm rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\"\n [placeholder]=\"\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n \"\n />\n }\n @if (col.filterType === 'select') {\n <label [for]=\"'shk-filter-' + col.field\" class=\"sr-only\">{{\n col.filterPlaceholder || ('common.search_placeholder' | transloco)\n }}</label>\n <select\n [id]=\"'shk-filter-' + col.field\"\n [formControlName]=\"col.field\"\n class=\"w-full px-2 py-1 text-xs rounded-md border _shk-border dark:[border-color:var(--shk-surface-200)] bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] appearance-none\"\n >\n <option [ngValue]=\"null\">{{ 'table.filterAll' | transloco }}</option>\n @for (opt of col.filterOptions || []; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @if (hasFilterValue(col.field) && col.filterType !== 'select') {\n <button\n type=\"button\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)]\"\n (click)=\"clearFilter(col.field)\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <div class=\"flex items-center justify-center\">\n <span>{{ col.header }}</span>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n @if (loading()) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div\n class=\"flex items-center justify-center\"\n role=\"status\"\n [attr.aria-label]=\"'common.loading' | transloco\"\n >\n <svg\n [lucideIcon]=\"getIcon('loader')\"\n class=\"w-6 h-6 animate-spin _shk-text-primary\"\n ></svg>\n </div>\n </td>\n </tr>\n </tbody>\n } @else if (effectiveDisplayedData().length === 0) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-100)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex flex-col items-center\">\n <div\n class=\"w-16 h-16 _shk-bg-surface-100 dark:[background-color:var(--shk-surface-100)] rounded-full flex items-center justify-center mb-4\"\n >\n <svg\n [lucideIcon]=\"getIcon('inbox')\"\n class=\"w-8 h-8 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n <p\n class=\"text-lg font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\"\n >\n {{ emptyMessage() || ('common.no_records' | transloco) }}\n </p>\n </div>\n </td>\n </tr>\n </tbody>\n } @else {\n <tbody>\n @for (rowData of effectiveDisplayedData(); track trackByRow(rowData, $index)) {\n <tr\n class=\"hover:[background-color:color-mix(in srgb,var(--shk-primary-50)30%,transparent)] dark:hover:[background-color:color-mix(in srgb,var(--shk-primary-900)10%,transparent)] transition-colors duration-200 border-b _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n @for (col of columnsWithActions(); track col.field) {\n <td\n [attr.data-shk-actions]=\"col.field === 'actions' ? '' : null\"\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)] whitespace-nowrap\"\n [style.overflow]=\"col.field === 'actions' ? 'visible' : 'hidden'\"\n [style.text-overflow]=\"col.field === 'actions' ? 'clip' : 'ellipsis'\"\n [style.--shk-col-width]=\"col.field !== 'actions' ? getColumnWidth(col) : null\"\n >\n @if (col.field !== 'actions') {\n @if (col.template === 'tag') {\n <shk-tag\n [value]=\"getTagValue(col, rowData)\"\n [severity]=\"getTagSeverity(col, rowData)\"\n [rounded]=\"getTagRounded(col, rowData)\"\n [icon]=\"getTagIcon(col, rowData)\"\n [style]=\"getTagStyle(col, rowData)\"\n />\n } @else if (col.format) {\n <span>{{ col.format(rowData) }}</span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n customTemplates()[col.field] || defaultTemplate;\n context: { $implicit: rowData, field: col.field }\n \"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-start gap-px\">\n @for (action of rowActions(); track $index) {\n @if (isRowActionVisible(action, rowData)) {\n <div class=\"relative group\">\n <shk-tooltip\n [text]=\"getRowActionLabel(action, rowData)\"\n position=\"top\"\n >\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData)\"\n [class]=\"\n 'table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-200)] rounded-md ' +\n getRowActionClass(action, rowData)\n \"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n [attr.aria-label]=\"getRowActionLabel(action, rowData)\"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n </shk-tooltip>\n </div>\n }\n }\n </div>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n }\n </table>\n <ng-template #defaultTemplate let-rowData let-field=\"field\">\n {{ truncate(rowData[field], 30) }}\n </ng-template>\n </div>\n <div\n class=\"flex items-center justify-between gap-4 px-2 min-[1440px]:px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-100)]\"\n >\n <div class=\"text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">\n {{ pageReport() }}\n </div>\n <div\n class=\"flex items-center gap-0.5\"\n role=\"navigation\"\n >\n <button\n type=\"button\"\n (click)=\"goToFirst()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span\n class=\"px-2 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-700)]\"\n >{{ currentPage() }} / {{ totalPages() }}</span\n >\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"goToLast()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n <div\n class=\"flex items-center gap-1.5 text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\"\n >\n <label [for]=\"'shk-rows-per-page'\">{{ 'table.rows' | transloco }}</label>\n <select\n id=\"shk-rows-per-page\"\n [value]=\"rowsPerPageLocal()\"\n (change)=\"onRowsPerPageChange($any($event.target).value)\"\n class=\"border _shk-border dark:[border-color:var(--shk-surface-200)] rounded px-2 py-1 text-xs bg-white dark:[background-color:var(--shk-surface-100)] _shk-text-surface-700 dark:[color:var(--shk-surface-700)] focus:outline-none focus:[border-color:var(--shk-primary-500)]\"\n >\n @for (opt of rowsPerPageOptions(); track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n</div>\n", styles: ["input:focus{outline:none}input[type=text]{font-weight:400!important}table th,table td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}td button svg{width:1.125rem!important;height:1.125rem!important}td button:disabled{opacity:.4;cursor:not-allowed}.table-action-btn svg{width:1rem!important;height:1rem!important;min-width:1rem!important;min-height:1rem!important}.table-action-btn{padding:.5rem!important;min-width:2rem!important;min-height:2rem!important;display:inline-flex!important;align-items:center!important;justify-content:center!important}.table-scroll-wrapper{width:100%;overflow-x:auto;overflow-y:hidden;border-radius:inherit;-webkit-overflow-scrolling:touch;container-type:inline-size}.table-scroll-wrapper table{table-layout:fixed;width:100%}@container (max-width: 768px){.table-scroll-wrapper{scroll-snap-type:x mandatory}.table-scroll-wrapper table{table-layout:auto;width:100%}.table-scroll-wrapper th:not([data-shk-actions]),.table-scroll-wrapper td:not([data-shk-actions]){min-width:var(--shk-col-width, 140px);max-width:var(--shk-col-width, 140px)}.table-scroll-wrapper th,.table-scroll-wrapper td{scroll-snap-align:start}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
2702
3063
  }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], rowsPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPage", required: false }] }], rowsPerPageOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageOptions", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], showActionRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActionRow", required: false }] }], customTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "customTemplates", required: false }] }], headerActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerActions", required: false }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], hasShadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasShadow", required: false }] }], defaultSortField: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSortField", required: false }] }], defaultSortOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSortOrder", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], serverSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "serverSide", required: false }] }], totalRecords: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRecords", required: false }] }], filters: [{ type: i0.Input, args: [{ isSignal: true, alias: "filters", required: false }] }], activeFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeFilter", required: false }] }], refresh: [{ type: i0.Output, args: ["refresh"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], filterClick: [{ type: i0.Output, args: ["filterClick"] }] } });
2703
3064
 
2704
- class TooltipComponent {
2705
- constructor() {
2706
- this.text = input('', ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
2707
- this.position = input('bottom', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
2708
- this.mode = input('hover', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
2709
- this.offset = input(8, ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
2710
- this.elementRef = inject(ElementRef);
2711
- this.tooltipX = signal(0, ...(ngDevMode ? [{ debugName: "tooltipX" }] : /* istanbul ignore next */ []));
2712
- this.tooltipY = signal(0, ...(ngDevMode ? [{ debugName: "tooltipY" }] : /* istanbul ignore next */ []));
2713
- }
2714
- onMouseEnter() {
2715
- if (this.mode() !== 'fixed')
2716
- return;
2717
- const host = this.elementRef.nativeElement;
2718
- const container = host.querySelector('.group');
2719
- if (!container)
2720
- return;
2721
- const rect = container.getBoundingClientRect();
2722
- const off = this.offset();
2723
- if (this.position() === 'right') {
2724
- this.tooltipX.set(rect.right + off);
2725
- this.tooltipY.set(rect.top + rect.height / 2);
2726
- }
2727
- else if (this.position() === 'bottom') {
2728
- this.tooltipX.set(rect.left + rect.width / 2);
2729
- this.tooltipY.set(rect.bottom + off);
2730
- }
2731
- else {
2732
- this.tooltipX.set(rect.left + rect.width / 2);
2733
- this.tooltipY.set(rect.top - off);
2734
- }
2735
- }
2736
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2737
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: TooltipComponent, isStandalone: true, selector: "shk-tooltip", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "onMouseEnter()" } }, ngImport: i0, template: `
2738
- <div class="relative group inline-flex">
2739
- <ng-content></ng-content>
2740
- @if (text() && mode() === 'hover') {
2741
- <span
2742
- class="absolute px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg"
2743
- [class.top-full]="position() === 'bottom'"
2744
- [class.bottom-full]="position() === 'top'"
2745
- [class.left-full]="position() === 'right'"
2746
- [class.mt-1]="position() === 'bottom'"
2747
- [class.mb-1]="position() === 'top'"
2748
- [class.ml-1]="position() === 'right'"
2749
- [class.left-1/2]="position() !== 'right'"
2750
- [class.-translate-x-1/2]="position() !== 'right'"
2751
- [class.top-1/2]="position() === 'right'"
2752
- [class.-translate-y-1/2]="position() === 'right'"
2753
- >
2754
- {{ text() }}
2755
- </span>
2756
- }
2757
- @if (text() && mode() === 'fixed') {
2758
- <span
2759
- class="fixed px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg"
2760
- [style.top.px]="tooltipY()"
2761
- [style.left.px]="tooltipX()"
2762
- [style.transform]="position() === 'right' ? 'translateY(-50%)' : ''"
2763
- >
2764
- {{ text() }}
2765
- </span>
2766
- }
2767
- </div>
2768
- `, isInline: true }); }
2769
- }
2770
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TooltipComponent, decorators: [{
2771
- type: Component,
2772
- args: [{
2773
- selector: 'shk-tooltip',
2774
- standalone: true,
2775
- template: `
2776
- <div class="relative group inline-flex">
2777
- <ng-content></ng-content>
2778
- @if (text() && mode() === 'hover') {
2779
- <span
2780
- class="absolute px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg"
2781
- [class.top-full]="position() === 'bottom'"
2782
- [class.bottom-full]="position() === 'top'"
2783
- [class.left-full]="position() === 'right'"
2784
- [class.mt-1]="position() === 'bottom'"
2785
- [class.mb-1]="position() === 'top'"
2786
- [class.ml-1]="position() === 'right'"
2787
- [class.left-1/2]="position() !== 'right'"
2788
- [class.-translate-x-1/2]="position() !== 'right'"
2789
- [class.top-1/2]="position() === 'right'"
2790
- [class.-translate-y-1/2]="position() === 'right'"
2791
- >
2792
- {{ text() }}
2793
- </span>
2794
- }
2795
- @if (text() && mode() === 'fixed') {
2796
- <span
2797
- class="fixed px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg"
2798
- [style.top.px]="tooltipY()"
2799
- [style.left.px]="tooltipX()"
2800
- [style.transform]="position() === 'right' ? 'translateY(-50%)' : ''"
2801
- >
2802
- {{ text() }}
2803
- </span>
2804
- }
2805
- </div>
2806
- `,
2807
- }]
2808
- }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], onMouseEnter: [{
2809
- type: HostListener,
2810
- args: ['mouseenter']
2811
- }] } });
2812
-
2813
3065
  const ICONS$1 = {
2814
3066
  house: LucideHouse,
2815
3067
  home: LucideHome,
@@ -3752,6 +4004,1038 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImpo
3752
4004
  args: [{ selector: 'shk-wizard', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, LucideDynamicIcon], template: "<div class=\"min-h-full bg-transparent\">\n\n <!-- ============ Sticky Header + Stepper ============ -->\n @if (showHeader()) {\n <div\n class=\"sticky top-0 z-20 bg-white dark:[background-color:var(--shk-surface-50)] border-b _shk-border dark:[border-color:var(--shk-surface-200)] shadow-sm\"\n >\n <div class=\"w-full px-4 sm:px-6 lg:px-8 py-4\">\n <div class=\"flex items-center gap-4 mb-4\">\n @if (showBackButton()) {\n <button\n type=\"button\"\n (click)=\"back.emit()\"\n [attr.aria-label]=\"backTooltip()\"\n [attr.title]=\"backTooltip()\"\n class=\"shk-wizard-icon-btn shrink-0\"\n >\n <svg [lucideIcon]=\"getIcon('arrow-left')\" class=\"w-4 h-4\"></svg>\n </button>\n }\n <div class=\"flex-1 min-w-0 flex items-center gap-2.5\">\n @if (headerIcon()) {\n <span class=\"w-9 h-9 rounded-lg _shk-bg-primary-soft _shk-text-primary flex items-center justify-center shrink-0\">\n <svg [lucideIcon]=\"getIcon(headerIcon())\" class=\"w-4 h-4\"></svg>\n </span>\n }\n <div class=\"min-w-0\">\n <h1 class=\"text-xl font-bold _shk-text-surface-800 dark:[color:var(--shk-surface-800)] truncate\">\n {{ headerTitle() }}\n </h1>\n @if (headerSubtitle()) {\n <p class=\"text-xs _shk-text-surface-500 dark:[color:var(--shk-surface-500)] mt-0.5 truncate\">\n {{ headerSubtitle() }}\n </p>\n }\n </div>\n </div>\n <ng-content select=\"[shkWizardHeaderActions]\"></ng-content>\n </div>\n\n <!-- Stepper -->\n @if (showStepper() && steps().length > 1) {\n <div class=\"flex items-center gap-2 w-full max-w-3xl mx-auto\">\n @for (step of steps(); track step.id; let i = $index, last = $last) {\n <div class=\"flex items-center gap-3 shrink-0\">\n <button\n type=\"button\"\n (click)=\"goTo(step.id)\"\n [disabled]=\"!stepperClickable() || currentIndex() < i\"\n [attr.aria-label]=\"step.label\"\n class=\"shk-wizard-stepper-dot\"\n [class._shk-bg-primary]=\"currentIndex() > i || currentIndex() === i\"\n [class.text-white]=\"currentIndex() > i || currentIndex() === i\"\n [class._shk-bg-surface-200]=\"currentIndex() < i\"\n [class._shk-text-surface-500]=\"currentIndex() < i\"\n [class.cursor-pointer]=\"stepperClickable() && currentIndex() >= i\"\n [class.cursor-default]=\"!stepperClickable() || currentIndex() < i\"\n >\n @if (currentIndex() > i) {\n <svg [lucideIcon]=\"getIcon(step.icon || 'check')\" class=\"w-4 h-4\"></svg>\n } @else if (step.icon) {\n <svg [lucideIcon]=\"getIcon(step.icon)\" class=\"w-4 h-4\"></svg>\n } @else {\n {{ i + 1 }}\n }\n </button>\n <span\n class=\"text-sm font-semibold transition-colors whitespace-nowrap hidden sm:inline\"\n [class._shk-text-surface-800]=\"currentIndex() === i\"\n [class._shk-text-surface-500]=\"currentIndex() !== i\"\n [class.dark:[color:var(--shk-surface-800)]]=\"currentIndex() === i\"\n [class.dark:[color:var(--shk-surface-500)]]=\"currentIndex() !== i\"\n >\n {{ step.label }}\n </span>\n </div>\n\n @if (!last) {\n <div class=\"flex-1 h-0.5 _shk-bg-surface-200 dark:[background-color:var(--shk-surface-200)] relative overflow-hidden rounded-full\">\n <div\n class=\"absolute inset-y-0 left-0 _shk-bg-primary transition-all duration-300\"\n [class.w-full]=\"currentIndex() > i\"\n [class.w-0]=\"currentIndex() <= i\"\n ></div>\n </div>\n }\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- ============ Active step content ============ -->\n <div class=\"w-full px-4 sm:px-6 lg:px-8 py-6 pb-28\">\n @if (currentIndex() >= 0) {\n @if (getStepTemplate(steps()[currentIndex()].id); as tpl) {\n <div class=\"shk-wizard-fade\" [style.view-transition-name]=\"'shk-wizard-step'\">\n <ng-container\n *ngTemplateOutlet=\"tpl; context: { step: currentStepObj(), index: currentIndex() }\"\n ></ng-container>\n </div>\n } @else {\n <p class=\"text-sm _shk-text-surface-400 dark:[color:var(--shk-surface-400)]\">\n No hay contenido definido para el paso \u00AB{{ steps()[currentIndex()].label }}\u00BB.\n </p>\n }\n }\n </div>\n\n <!-- ============ Sticky Footer ============ -->\n @if (showFooter() && steps().length > 0) {\n <div\n class=\"sticky bottom-0 z-20 bg-white dark:[background-color:var(--shk-surface-50)] border-t _shk-border dark:[border-color:var(--shk-surface-200)] shadow-[0_-4px_12px_rgba(0,0,0,0.04)] mt-8\"\n >\n <div class=\"w-full px-4 sm:px-6 lg:px-8 py-4 flex items-center justify-end gap-3\">\n @if (showStepCounter()) {\n <span class=\"text-sm _shk-text-surface-500 dark:[color:var(--shk-surface-500)] mr-auto\">\n {{ stepCounterPrefix() }} {{ currentIndex() + 1 }} / {{ steps().length }}\n </span>\n }\n\n @if (isFirstStep()) {\n @if (showCancel()) {\n <button type=\"button\" (click)=\"cancel.emit()\" class=\"shk-wizard-btn-secondary\">\n @if (cancelIcon()) {\n <svg [lucideIcon]=\"getIcon(cancelIcon())\" class=\"w-4 h-4\"></svg>\n }\n {{ cancelLabel() }}\n </button>\n }\n } @else {\n <button type=\"button\" (click)=\"onPrev()\" class=\"shk-wizard-btn-secondary\">\n <svg [lucideIcon]=\"getIcon(prevIcon())\" class=\"w-4 h-4\"></svg>\n {{ prevLabel() }}\n </button>\n }\n\n <button\n type=\"button\"\n (click)=\"onPrimary()\"\n [disabled]=\"primaryDisabled() || primaryLoading()\"\n class=\"shk-wizard-btn-primary\"\n >\n @if (primaryLoading()) {\n <svg [lucideIcon]=\"getIcon('loader-circle')\" class=\"w-4 h-4 shk-wizard-spin\"></svg>\n } @else {\n @if (primaryActionIcon()) {\n <svg [lucideIcon]=\"getIcon(primaryActionIcon())\" class=\"w-4 h-4\"></svg>\n }\n }\n {{ primaryActionLabel() }}\n </button>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block}.shk-wizard-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:9999px;color:var(--shk-surface-600);transition:background-color .15s ease-in-out,color .15s ease-in-out}.shk-wizard-icon-btn:hover:not(:disabled){background-color:var(--shk-surface-100);color:var(--shk-surface-800)}.shk-wizard-icon-btn:disabled{cursor:default}.shk-wizard-icon-btn svg{width:1rem!important;height:1rem!important}.shk-wizard-stepper-dot{display:flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border-radius:9999px;font-size:.875rem;font-weight:700;flex-shrink:0;transition:background-color .15s ease-in-out,color .15s ease-in-out}.shk-wizard-stepper-dot svg{width:1rem!important;height:1rem!important}.shk-wizard-btn-secondary{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;border-radius:.5rem;font-size:.875rem;font-weight:500;color:var(--shk-surface-700);background-color:transparent;border:1px solid var(--shk-surface-200);transition:background-color .15s ease-in-out,border-color .15s ease-in-out,color .15s ease-in-out}.shk-wizard-btn-secondary:hover:not(:disabled){background-color:var(--shk-surface-100);border-color:var(--shk-surface-300);color:var(--shk-surface-900)}.shk-wizard-btn-secondary:disabled{opacity:.5;cursor:not-allowed}.shk-wizard-btn-secondary svg{width:1rem!important;height:1rem!important}.shk-wizard-btn-primary{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;border-radius:.5rem;font-size:.875rem;font-weight:500;color:#fff;background-color:var(--shk-primary-600);box-shadow:0 6px 20px -6px color-mix(in srgb,var(--shk-primary-600) 55%,transparent);transition:background-color .15s ease-in-out,opacity .15s ease-in-out}.shk-wizard-btn-primary:hover:not(:disabled){background-color:var(--shk-primary-700)}.shk-wizard-btn-primary:disabled{opacity:.5;cursor:not-allowed;box-shadow:none}.shk-wizard-btn-primary svg{width:1rem!important;height:1rem!important}.shk-wizard-spin{animation:shk-wizard-spin .8s linear infinite}@keyframes shk-wizard-spin{to{transform:rotate(360deg)}}.shk-wizard-fade{animation:shk-wizard-fade .25s ease-out}@keyframes shk-wizard-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n"] }]
3753
4005
  }], propDecorators: { steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: false }] }], currentStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentStep", required: false }] }, { type: i0.Output, args: ["currentStepChange"] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], headerTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerTitle", required: false }] }], headerSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerSubtitle", required: false }] }], showBackButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBackButton", required: false }] }], backTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "backTooltip", required: false }] }], headerIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerIcon", required: false }] }], showStepper: [{ type: i0.Input, args: [{ isSignal: true, alias: "showStepper", required: false }] }], stepperClickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepperClickable", required: false }] }], showFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooter", required: false }] }], showStepCounter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showStepCounter", required: false }] }], stepCounterPrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepCounterPrefix", required: false }] }], showCancel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCancel", required: false }] }], advanceOnNext: [{ type: i0.Input, args: [{ isSignal: true, alias: "advanceOnNext", required: false }] }], prevLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "prevLabel", required: false }] }], prevIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "prevIcon", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], cancelIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelIcon", required: false }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], nextIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextIcon", required: false }] }], finishLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "finishLabel", required: false }] }], finishIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "finishIcon", required: false }] }], primaryLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryLabel", required: false }] }], primaryIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryIcon", required: false }] }], primaryLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryLoading", required: false }] }], primaryDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryDisabled", required: false }] }], back: [{ type: i0.Output, args: ["back"] }], cancel: [{ type: i0.Output, args: ["cancel"] }], prev: [{ type: i0.Output, args: ["prev"] }], next: [{ type: i0.Output, args: ["next"] }], finish: [{ type: i0.Output, args: ["finish"] }], stepChange: [{ type: i0.Output, args: ["stepChange"] }], stepContents: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ShkWizardStepContent), { isSignal: true }] }] } });
3754
4006
 
4007
+ /** Placeholder block shown while content is loading. */
4008
+ class SkeletonComponent {
4009
+ constructor() {
4010
+ /** Shape of the skeleton: rectangle (default) or circle. */
4011
+ this.shape = input('rectangle', ...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
4012
+ /** When shape is circle, sets both width and height. */
4013
+ this.size = input('', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
4014
+ /** Width of the rectangle skeleton. */
4015
+ this.width = input('100%', ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
4016
+ /** Height of the rectangle skeleton. */
4017
+ this.height = input('1rem', ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
4018
+ /** Custom border radius (rectangle only). */
4019
+ this.borderRadius = input('', ...(ngDevMode ? [{ debugName: "borderRadius" }] : /* istanbul ignore next */ []));
4020
+ /** Animation effect: 'wave' (default), 'pulse' or 'none'. */
4021
+ this.animation = input('wave', ...(ngDevMode ? [{ debugName: "animation" }] : /* istanbul ignore next */ []));
4022
+ /** Inline styles applied to the skeleton block. */
4023
+ this.style = input({}, ...(ngDevMode ? [{ debugName: "style" }] : /* istanbul ignore next */ []));
4024
+ /** Extra CSS classes. */
4025
+ this.styleClass = input('', ...(ngDevMode ? [{ debugName: "styleClass" }] : /* istanbul ignore next */ []));
4026
+ this.rootStyle = computed(() => {
4027
+ const s = {};
4028
+ if (this.shape() === 'circle') {
4029
+ s['width'] = this.size() || this.width();
4030
+ s['height'] = this.size() || this.height();
4031
+ s['borderRadius'] = '9999px';
4032
+ }
4033
+ else {
4034
+ s['width'] = this.width();
4035
+ s['height'] = this.height();
4036
+ s['borderRadius'] = this.borderRadius() || '0.25rem';
4037
+ }
4038
+ return { ...s, ...this.style() };
4039
+ }, ...(ngDevMode ? [{ debugName: "rootStyle" }] : /* istanbul ignore next */ []));
4040
+ }
4041
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4042
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.18", type: SkeletonComponent, isStandalone: true, selector: "shk-skeleton", inputs: { shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, borderRadius: { classPropertyName: "borderRadius", publicName: "borderRadius", isSignal: true, isRequired: false, transformFunction: null }, animation: { classPropertyName: "animation", publicName: "animation", isSignal: true, isRequired: false, transformFunction: null }, style: { classPropertyName: "style", publicName: "style", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4043
+ <div
4044
+ [class]="'shk-skeleton ' + styleClass()"
4045
+ [class.shk-skeleton-circle]="shape() === 'circle'"
4046
+ [class.shk-skeleton-animate-wave]="animation() === 'wave'"
4047
+ [class.shk-skeleton-animate-pulse]="animation() === 'pulse'"
4048
+ [style]="rootStyle()"
4049
+ aria-hidden="true"
4050
+ ></div>
4051
+ `, isInline: true, styles: [":host{display:block}.shk-skeleton{position:relative;overflow:hidden;background-color:var(--shk-skeleton-bg)}.shk-skeleton-circle{border-radius:9999px}.shk-skeleton-animate-wave:after{content:\"\";position:absolute;inset:0;background-image:linear-gradient(90deg,transparent,var(--shk-skeleton-highlight) 50%,transparent);animation:shk-skeleton-wave 1.6s ease-in-out infinite}.shk-skeleton-animate-pulse{animation:shk-skeleton-pulse 2s ease-in-out infinite}@keyframes shk-skeleton-wave{0%{transform:translate(-100%)}to{transform:translate(100%)}}@keyframes shk-skeleton-pulse{0%,to{opacity:1}50%{opacity:.55}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4052
+ }
4053
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SkeletonComponent, decorators: [{
4054
+ type: Component,
4055
+ args: [{ selector: 'shk-skeleton', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
4056
+ <div
4057
+ [class]="'shk-skeleton ' + styleClass()"
4058
+ [class.shk-skeleton-circle]="shape() === 'circle'"
4059
+ [class.shk-skeleton-animate-wave]="animation() === 'wave'"
4060
+ [class.shk-skeleton-animate-pulse]="animation() === 'pulse'"
4061
+ [style]="rootStyle()"
4062
+ aria-hidden="true"
4063
+ ></div>
4064
+ `, styles: [":host{display:block}.shk-skeleton{position:relative;overflow:hidden;background-color:var(--shk-skeleton-bg)}.shk-skeleton-circle{border-radius:9999px}.shk-skeleton-animate-wave:after{content:\"\";position:absolute;inset:0;background-image:linear-gradient(90deg,transparent,var(--shk-skeleton-highlight) 50%,transparent);animation:shk-skeleton-wave 1.6s ease-in-out infinite}.shk-skeleton-animate-pulse{animation:shk-skeleton-pulse 2s ease-in-out infinite}@keyframes shk-skeleton-wave{0%{transform:translate(-100%)}to{transform:translate(100%)}}@keyframes shk-skeleton-pulse{0%,to{opacity:1}50%{opacity:.55}}\n"] }]
4065
+ }], propDecorators: { shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], borderRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderRadius", required: false }] }], animation: [{ type: i0.Input, args: [{ isSignal: true, alias: "animation", required: false }] }], style: [{ type: i0.Input, args: [{ isSignal: true, alias: "style", required: false }] }], styleClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "styleClass", required: false }] }] } });
4066
+
4067
+ /** Generic button with icon, severity variants, loading state and sizes. */
4068
+ class ButtonComponent {
4069
+ constructor() {
4070
+ /** Text shown inside the button. */
4071
+ this.label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
4072
+ /** Optional leading lucide icon name. */
4073
+ this.icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
4074
+ /** Position of the icon relative to the label. */
4075
+ this.iconPos = input('left', ...(ngDevMode ? [{ debugName: "iconPos" }] : /* istanbul ignore next */ []));
4076
+ /** Color variant. Defaults to 'primary'. */
4077
+ this.severity = input('primary', ...(ngDevMode ? [{ debugName: "severity" }] : /* istanbul ignore next */ []));
4078
+ /** Transparent background with a colored border. */
4079
+ this.outlined = input(false, ...(ngDevMode ? [{ debugName: "outlined" }] : /* istanbul ignore next */ []));
4080
+ /** Borderless button with a colored label. */
4081
+ this.text = input(false, ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
4082
+ /** Adds a soft shadow. */
4083
+ this.raised = input(false, ...(ngDevMode ? [{ debugName: "raised" }] : /* istanbul ignore next */ []));
4084
+ /** Fully rounded corners (pill). */
4085
+ this.rounded = input(false, ...(ngDevMode ? [{ debugName: "rounded" }] : /* istanbul ignore next */ []));
4086
+ /** Size variant. */
4087
+ this.size = input(undefined, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
4088
+ /** Disables the button. */
4089
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
4090
+ /** Shows a spinner and disables the button. */
4091
+ this.loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
4092
+ /** Icon shown while loading. Defaults to 'refresh'. */
4093
+ this.loadingIcon = input('refresh', ...(ngDevMode ? [{ debugName: "loadingIcon" }] : /* istanbul ignore next */ []));
4094
+ /** Optional text shown instead of the label while loading. */
4095
+ this.loadingText = input('', ...(ngDevMode ? [{ debugName: "loadingText" }] : /* istanbul ignore next */ []));
4096
+ /** Native button type. */
4097
+ this.type = input('button', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
4098
+ /** Accessible label when the button is icon-only. */
4099
+ this.ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
4100
+ /** Native title tooltip. */
4101
+ this.title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
4102
+ /** Inline styles applied to the button. */
4103
+ this.style = input({}, ...(ngDevMode ? [{ debugName: "style" }] : /* istanbul ignore next */ []));
4104
+ /** Extra CSS classes. */
4105
+ this.styleClass = input('', ...(ngDevMode ? [{ debugName: "styleClass" }] : /* istanbul ignore next */ []));
4106
+ /** Emitted on click with the original MouseEvent. */
4107
+ this.onClick = output();
4108
+ this.getIcon = getIcon$6;
4109
+ this.iconOnly = computed(() => !this.label() && !!this.icon(), ...(ngDevMode ? [{ debugName: "iconOnly" }] : /* istanbul ignore next */ []));
4110
+ }
4111
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4112
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: ButtonComponent, isStandalone: true, selector: "shk-button", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconPos: { classPropertyName: "iconPos", publicName: "iconPos", isSignal: true, isRequired: false, transformFunction: null }, severity: { classPropertyName: "severity", publicName: "severity", isSignal: true, isRequired: false, transformFunction: null }, outlined: { classPropertyName: "outlined", publicName: "outlined", isSignal: true, isRequired: false, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, raised: { classPropertyName: "raised", publicName: "raised", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, loadingIcon: { classPropertyName: "loadingIcon", publicName: "loadingIcon", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, style: { classPropertyName: "style", publicName: "style", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClick: "onClick" }, ngImport: i0, template: `
4113
+ <button
4114
+ [attr.type]="type()"
4115
+ class="shk-button"
4116
+ [class.shk-button-primary]="severity() === 'primary' || !severity()"
4117
+ [class.shk-button-secondary]="severity() === 'secondary'"
4118
+ [class.shk-button-success]="severity() === 'success'"
4119
+ [class.shk-button-info]="severity() === 'info'"
4120
+ [class.shk-button-warning]="severity() === 'warning'"
4121
+ [class.shk-button-danger]="severity() === 'danger'"
4122
+ [class.shk-button-help]="severity() === 'help'"
4123
+ [class.shk-button-contrast]="severity() === 'contrast'"
4124
+ [class.shk-button-outlined]="outlined()"
4125
+ [class.shk-button-text]="text()"
4126
+ [class.shk-button-raised]="raised()"
4127
+ [class.shk-button-rounded]="rounded()"
4128
+ [class.shk-button-sm]="size() === 'small'"
4129
+ [class.shk-button-lg]="size() === 'large'"
4130
+ [class.shk-button-icon-right]="iconPos() === 'right'"
4131
+ [class.shk-button-icon-top]="iconPos() === 'top'"
4132
+ [class.shk-button-icon-bottom]="iconPos() === 'bottom'"
4133
+ [class.shk-button-icon-only]="iconOnly()"
4134
+ [class.shk-button-loading]="loading()"
4135
+ [class]="styleClass()"
4136
+ [style]="style()"
4137
+ [disabled]="disabled() || loading()"
4138
+ [attr.aria-label]="ariaLabel() || undefined"
4139
+ [attr.title]="title() || undefined"
4140
+ (click)="onClick.emit($event)"
4141
+ >
4142
+ @if (loading()) {
4143
+ <svg [lucideIcon]="getIcon(loadingIcon())" class="shk-button-icon shk-button-spin"></svg>
4144
+ } @else if (icon()) {
4145
+ <svg [lucideIcon]="getIcon(icon())" class="shk-button-icon"></svg>
4146
+ }
4147
+ @if (label()) {
4148
+ <span>{{ loading() && loadingText() ? loadingText() : label() }}</span>
4149
+ }
4150
+ <ng-content></ng-content>
4151
+ </button>
4152
+ `, isInline: true, styles: [":host{display:inline-block}.shk-button{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;padding:.5rem 1rem;border-radius:.5rem;border:1px solid transparent;font-size:.875rem;font-weight:500;line-height:1.25rem;cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,color .15s ease-in-out,box-shadow .15s ease-in-out,opacity .15s ease-in-out}.shk-button:focus-visible{outline:2px solid var(--shk-primary-400);outline-offset:2px}.shk-button:disabled{opacity:.55;cursor:not-allowed}.shk-button-icon{width:1rem;height:1rem;flex-shrink:0}.shk-button-primary{--shk-btn-bg: var(--shk-primary-600);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-primary-600)}.shk-button-secondary{--shk-btn-bg: var(--shk-surface-100);--shk-btn-color: var(--shk-surface-700);--shk-btn-border: var(--shk-surface-200)}.shk-button-success{--shk-btn-bg: var(--shk-success-600);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-success-600)}.shk-button-info{--shk-btn-bg: var(--shk-info-600);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-info-600)}.shk-button-warning{--shk-btn-bg: var(--shk-warning-600);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-warning-600)}.shk-button-danger{--shk-btn-bg: var(--shk-error-600);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-error-600)}.shk-button-help{--shk-btn-bg: var(--shk-primary-500);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-primary-500)}.shk-button-contrast{--shk-btn-bg: var(--shk-surface-900);--shk-btn-color: var(--shk-surface-50);--shk-btn-border: var(--shk-surface-900)}.shk-button{background-color:var(--shk-btn-bg);color:var(--shk-btn-color);border-color:var(--shk-btn-border)}.shk-button:hover:not(:disabled){background-color:color-mix(in srgb,var(--shk-btn-bg) 88%,#000000)}.shk-button-outlined,.shk-button-text{background-color:transparent}.shk-button-outlined{border-color:var(--shk-btn-color)}.shk-button-text{border-color:transparent}.shk-button-text:hover:not(:disabled),.shk-button-outlined:hover:not(:disabled){background-color:color-mix(in srgb,var(--shk-btn-color) 12%,transparent)}.shk-button-raised{box-shadow:0 1px 2px #0000000f,0 4px 8px #0000001a}.shk-button-rounded{border-radius:9999px}.shk-button-sm{padding:.375rem .75rem;font-size:.8125rem}.shk-button-sm .shk-button-icon{width:.875rem;height:.875rem}.shk-button-lg{padding:.75rem 1.5rem;font-size:1rem}.shk-button-lg .shk-button-icon{width:1.25rem;height:1.25rem}.shk-button-icon-only{padding:.5rem}.shk-button-sm.shk-button-icon-only{padding:.375rem}.shk-button-lg.shk-button-icon-only{padding:.75rem}.shk-button-icon-right{flex-direction:row-reverse}.shk-button-icon-top{flex-direction:column}.shk-button-icon-bottom{flex-direction:column-reverse}.shk-button-loading .shk-button-spin{animation:shk-button-spin 1s linear infinite}@keyframes shk-button-spin{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4153
+ }
4154
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ButtonComponent, decorators: [{
4155
+ type: Component,
4156
+ args: [{ selector: 'shk-button', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [LucideDynamicIcon], template: `
4157
+ <button
4158
+ [attr.type]="type()"
4159
+ class="shk-button"
4160
+ [class.shk-button-primary]="severity() === 'primary' || !severity()"
4161
+ [class.shk-button-secondary]="severity() === 'secondary'"
4162
+ [class.shk-button-success]="severity() === 'success'"
4163
+ [class.shk-button-info]="severity() === 'info'"
4164
+ [class.shk-button-warning]="severity() === 'warning'"
4165
+ [class.shk-button-danger]="severity() === 'danger'"
4166
+ [class.shk-button-help]="severity() === 'help'"
4167
+ [class.shk-button-contrast]="severity() === 'contrast'"
4168
+ [class.shk-button-outlined]="outlined()"
4169
+ [class.shk-button-text]="text()"
4170
+ [class.shk-button-raised]="raised()"
4171
+ [class.shk-button-rounded]="rounded()"
4172
+ [class.shk-button-sm]="size() === 'small'"
4173
+ [class.shk-button-lg]="size() === 'large'"
4174
+ [class.shk-button-icon-right]="iconPos() === 'right'"
4175
+ [class.shk-button-icon-top]="iconPos() === 'top'"
4176
+ [class.shk-button-icon-bottom]="iconPos() === 'bottom'"
4177
+ [class.shk-button-icon-only]="iconOnly()"
4178
+ [class.shk-button-loading]="loading()"
4179
+ [class]="styleClass()"
4180
+ [style]="style()"
4181
+ [disabled]="disabled() || loading()"
4182
+ [attr.aria-label]="ariaLabel() || undefined"
4183
+ [attr.title]="title() || undefined"
4184
+ (click)="onClick.emit($event)"
4185
+ >
4186
+ @if (loading()) {
4187
+ <svg [lucideIcon]="getIcon(loadingIcon())" class="shk-button-icon shk-button-spin"></svg>
4188
+ } @else if (icon()) {
4189
+ <svg [lucideIcon]="getIcon(icon())" class="shk-button-icon"></svg>
4190
+ }
4191
+ @if (label()) {
4192
+ <span>{{ loading() && loadingText() ? loadingText() : label() }}</span>
4193
+ }
4194
+ <ng-content></ng-content>
4195
+ </button>
4196
+ `, styles: [":host{display:inline-block}.shk-button{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;padding:.5rem 1rem;border-radius:.5rem;border:1px solid transparent;font-size:.875rem;font-weight:500;line-height:1.25rem;cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,color .15s ease-in-out,box-shadow .15s ease-in-out,opacity .15s ease-in-out}.shk-button:focus-visible{outline:2px solid var(--shk-primary-400);outline-offset:2px}.shk-button:disabled{opacity:.55;cursor:not-allowed}.shk-button-icon{width:1rem;height:1rem;flex-shrink:0}.shk-button-primary{--shk-btn-bg: var(--shk-primary-600);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-primary-600)}.shk-button-secondary{--shk-btn-bg: var(--shk-surface-100);--shk-btn-color: var(--shk-surface-700);--shk-btn-border: var(--shk-surface-200)}.shk-button-success{--shk-btn-bg: var(--shk-success-600);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-success-600)}.shk-button-info{--shk-btn-bg: var(--shk-info-600);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-info-600)}.shk-button-warning{--shk-btn-bg: var(--shk-warning-600);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-warning-600)}.shk-button-danger{--shk-btn-bg: var(--shk-error-600);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-error-600)}.shk-button-help{--shk-btn-bg: var(--shk-primary-500);--shk-btn-color: #ffffff;--shk-btn-border: var(--shk-primary-500)}.shk-button-contrast{--shk-btn-bg: var(--shk-surface-900);--shk-btn-color: var(--shk-surface-50);--shk-btn-border: var(--shk-surface-900)}.shk-button{background-color:var(--shk-btn-bg);color:var(--shk-btn-color);border-color:var(--shk-btn-border)}.shk-button:hover:not(:disabled){background-color:color-mix(in srgb,var(--shk-btn-bg) 88%,#000000)}.shk-button-outlined,.shk-button-text{background-color:transparent}.shk-button-outlined{border-color:var(--shk-btn-color)}.shk-button-text{border-color:transparent}.shk-button-text:hover:not(:disabled),.shk-button-outlined:hover:not(:disabled){background-color:color-mix(in srgb,var(--shk-btn-color) 12%,transparent)}.shk-button-raised{box-shadow:0 1px 2px #0000000f,0 4px 8px #0000001a}.shk-button-rounded{border-radius:9999px}.shk-button-sm{padding:.375rem .75rem;font-size:.8125rem}.shk-button-sm .shk-button-icon{width:.875rem;height:.875rem}.shk-button-lg{padding:.75rem 1.5rem;font-size:1rem}.shk-button-lg .shk-button-icon{width:1.25rem;height:1.25rem}.shk-button-icon-only{padding:.5rem}.shk-button-sm.shk-button-icon-only{padding:.375rem}.shk-button-lg.shk-button-icon-only{padding:.75rem}.shk-button-icon-right{flex-direction:row-reverse}.shk-button-icon-top{flex-direction:column}.shk-button-icon-bottom{flex-direction:column-reverse}.shk-button-loading .shk-button-spin{animation:shk-button-spin 1s linear infinite}@keyframes shk-button-spin{to{transform:rotate(360deg)}}\n"] }]
4197
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconPos: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconPos", required: false }] }], severity: [{ type: i0.Input, args: [{ isSignal: true, alias: "severity", required: false }] }], outlined: [{ type: i0.Input, args: [{ isSignal: true, alias: "outlined", required: false }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], raised: [{ type: i0.Input, args: [{ isSignal: true, alias: "raised", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], loadingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingIcon", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], style: [{ type: i0.Input, args: [{ isSignal: true, alias: "style", required: false }] }], styleClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "styleClass", required: false }] }], onClick: [{ type: i0.Output, args: ["onClick"] }] } });
4198
+
4199
+ /** SVG radial knob with drag and keyboard support. Implements ControlValueAccessor. */
4200
+ class KnobComponent {
4201
+ constructor() {
4202
+ /** Current value. Two-way bindable with [(value)]. */
4203
+ this.value = model(0, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4204
+ /** Minimum value. */
4205
+ this.min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
4206
+ /** Maximum value. */
4207
+ this.max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
4208
+ /** Increment used by arrows and pointer drag rounding. */
4209
+ this.step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
4210
+ /** Diameter of the knob in px. */
4211
+ this.size = input(100, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
4212
+ /** Thickness of the arc stroke. */
4213
+ this.strokeWidth = input(14, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : /* istanbul ignore next */ []));
4214
+ /** Color of the filled arc. */
4215
+ this.valueColor = input('var(--shk-primary-600)', ...(ngDevMode ? [{ debugName: "valueColor" }] : /* istanbul ignore next */ []));
4216
+ /** Color of the track arc. */
4217
+ this.rangeColor = input('var(--shk-surface-200)', ...(ngDevMode ? [{ debugName: "rangeColor" }] : /* istanbul ignore next */ []));
4218
+ /** Whether the current value is shown in the center. */
4219
+ this.showValue = input(true, ...(ngDevMode ? [{ debugName: "showValue" }] : /* istanbul ignore next */ []));
4220
+ /** Disables user interaction. */
4221
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
4222
+ /** Blocks editing but keeps the control styled as enabled. */
4223
+ this.readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
4224
+ /** Accessible label. */
4225
+ this.ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
4226
+ /** Emitted when the knob value changes. */
4227
+ this.change = output();
4228
+ /** Emitted when the user releases the pointer. */
4229
+ this.end = output();
4230
+ this.knobSvg = viewChild('knobSvg', ...(ngDevMode ? [{ debugName: "knobSvg" }] : /* istanbul ignore next */ []));
4231
+ this.onChange = () => { };
4232
+ this.onTouched = () => { };
4233
+ this.dragging = false;
4234
+ this.formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : /* istanbul ignore next */ []));
4235
+ /** Effective disabled state combining the `disabled` input and the form binding. */
4236
+ this.effectiveDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
4237
+ this.r = computed(() => (this.size() - this.strokeWidth()) / 2, ...(ngDevMode ? [{ debugName: "r" }] : /* istanbul ignore next */ []));
4238
+ this.center = computed(() => this.size() / 2, ...(ngDevMode ? [{ debugName: "center" }] : /* istanbul ignore next */ []));
4239
+ this.fraction = computed(() => {
4240
+ const range = this.max() - this.min();
4241
+ if (range <= 0)
4242
+ return 0;
4243
+ return Math.max(0, Math.min(1, (this.value() - this.min()) / range));
4244
+ }, ...(ngDevMode ? [{ debugName: "fraction" }] : /* istanbul ignore next */ []));
4245
+ this.rangeArc = computed(() => this.getArcPath(135, 405), ...(ngDevMode ? [{ debugName: "rangeArc" }] : /* istanbul ignore next */ []));
4246
+ this.valueArc = computed(() => this.getArcPath(135, 135 + 270 * this.fraction()), ...(ngDevMode ? [{ debugName: "valueArc" }] : /* istanbul ignore next */ []));
4247
+ }
4248
+ writeValue(value) {
4249
+ this.value.set(this.clamp(Number(value) || this.min()));
4250
+ }
4251
+ registerOnChange(fn) {
4252
+ this.onChange = fn;
4253
+ }
4254
+ registerOnTouched(fn) {
4255
+ this.onTouched = fn;
4256
+ }
4257
+ setDisabledState(isDisabled) {
4258
+ this.formDisabled.set(isDisabled);
4259
+ }
4260
+ onPointerDown(event) {
4261
+ if (this.effectiveDisabled() || this.readonly())
4262
+ return;
4263
+ this.dragging = true;
4264
+ this.knobSvg()?.nativeElement.setPointerCapture(event.pointerId);
4265
+ this.applyEvent(event);
4266
+ }
4267
+ onPointerMove(event) {
4268
+ if (!this.dragging)
4269
+ return;
4270
+ this.applyEvent(event);
4271
+ }
4272
+ onPointerUp(event) {
4273
+ if (!this.dragging)
4274
+ return;
4275
+ this.dragging = false;
4276
+ try {
4277
+ this.knobSvg()?.nativeElement.releasePointerCapture(event.pointerId);
4278
+ }
4279
+ catch {
4280
+ /* noop */
4281
+ }
4282
+ this.change.emit(this.value());
4283
+ this.end.emit(this.value());
4284
+ }
4285
+ updateByKey(step) {
4286
+ if (this.effectiveDisabled() || this.readonly())
4287
+ return;
4288
+ this.setValue(this.value() + step);
4289
+ this.onTouched();
4290
+ }
4291
+ applyEvent(event) {
4292
+ const svg = this.knobSvg()?.nativeElement;
4293
+ if (!svg)
4294
+ return;
4295
+ const rect = svg.getBoundingClientRect();
4296
+ const cx = rect.left + rect.width / 2;
4297
+ const cy = rect.top + rect.height / 2;
4298
+ const angle = Math.atan2(event.clientY - cy, event.clientX - cx);
4299
+ const deg = ((angle * 180) / Math.PI + 360) % 360;
4300
+ let t = deg - 135;
4301
+ if (t < 0)
4302
+ t += 360;
4303
+ let fraction;
4304
+ if (t > 270) {
4305
+ fraction = 360 - t < t - 270 ? 0 : 1;
4306
+ }
4307
+ else {
4308
+ fraction = t / 270;
4309
+ }
4310
+ const raw = this.min() + fraction * (this.max() - this.min());
4311
+ this.setValue(raw);
4312
+ this.onTouched();
4313
+ }
4314
+ setValue(raw) {
4315
+ const rounded = Math.round(raw / this.step()) * this.step();
4316
+ const clamped = this.clamp(rounded);
4317
+ this.value.set(clamped);
4318
+ this.onChange(clamped);
4319
+ }
4320
+ clamp(value) {
4321
+ return Math.max(this.min(), Math.min(this.max(), value));
4322
+ }
4323
+ getArcPath(startDeg, endDeg) {
4324
+ const cx = this.center();
4325
+ const cy = this.center();
4326
+ const r = this.r();
4327
+ const start = this.anglePoint(cx, cy, r, startDeg);
4328
+ const end = this.anglePoint(cx, cy, r, endDeg);
4329
+ const largeArc = endDeg - startDeg > 180 ? '1' : '0';
4330
+ return `M ${start.x.toFixed(2)} ${start.y.toFixed(2)} A ${r.toFixed(2)} ${r.toFixed(2)} 0 ${largeArc} 1 ${end.x.toFixed(2)} ${end.y.toFixed(2)}`;
4331
+ }
4332
+ anglePoint(cx, cy, r, angleDeg) {
4333
+ const rad = (Math.PI * angleDeg) / 180;
4334
+ return {
4335
+ x: cx + r * Math.cos(rad),
4336
+ y: cy + r * Math.sin(rad),
4337
+ };
4338
+ }
4339
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: KnobComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4340
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: KnobComponent, isStandalone: true, selector: "shk-knob", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, valueColor: { classPropertyName: "valueColor", publicName: "valueColor", isSignal: true, isRequired: false, transformFunction: null }, rangeColor: { classPropertyName: "rangeColor", publicName: "rangeColor", isSignal: true, isRequired: false, transformFunction: null }, showValue: { classPropertyName: "showValue", publicName: "showValue", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", change: "change", end: "end" }, providers: [
4341
+ {
4342
+ provide: NG_VALUE_ACCESSOR,
4343
+ useExisting: forwardRef(() => KnobComponent),
4344
+ multi: true,
4345
+ },
4346
+ ], viewQueries: [{ propertyName: "knobSvg", first: true, predicate: ["knobSvg"], descendants: true, isSignal: true }], ngImport: i0, template: `
4347
+ <div
4348
+ class="shk-knob"
4349
+ [class.shk-knob-disabled]="effectiveDisabled()"
4350
+ [style]="{ width: size() + 'px', height: size() + 'px' }"
4351
+ >
4352
+ <svg
4353
+ #knobSvg
4354
+ class="shk-knob-svg"
4355
+ [attr.width]="size()"
4356
+ [attr.height]="size()"
4357
+ [attr.viewBox]="'0 0 ' + size() + ' ' + size()"
4358
+ role="slider"
4359
+ tabindex="0"
4360
+ [attr.aria-label]="ariaLabel() || 'Valor'"
4361
+ [attr.aria-valuemin]="min()"
4362
+ [attr.aria-valuemax]="max()"
4363
+ [attr.aria-valuenow]="value()"
4364
+ [class.shk-knob-svg-focusable]="!effectiveDisabled()"
4365
+ (pointerdown)="onPointerDown($event)"
4366
+ (pointermove)="onPointerMove($event)"
4367
+ (pointerup)="onPointerUp($event)"
4368
+ (pointercancel)="onPointerUp($event)"
4369
+ (keydown.arrowup)="updateByKey(step())"
4370
+ (keydown.arrowright)="updateByKey(step())"
4371
+ (keydown.arrowdown)="updateByKey(-step())"
4372
+ (keydown.arrowleft)="updateByKey(-step())"
4373
+ >
4374
+ <path
4375
+ [attr.d]="rangeArc()"
4376
+ fill="none"
4377
+ [style.stroke]="rangeColor()"
4378
+ [attr.stroke-width]="strokeWidth()"
4379
+ stroke-linecap="round"
4380
+ ></path>
4381
+ <path
4382
+ [attr.d]="valueArc()"
4383
+ fill="none"
4384
+ [style.stroke]="valueColor()"
4385
+ [attr.stroke-width]="strokeWidth()"
4386
+ stroke-linecap="round"
4387
+ ></path>
4388
+ </svg>
4389
+ @if (showValue()) {
4390
+ <div class="shk-knob-label">{{ value() }}</div>
4391
+ }
4392
+ </div>
4393
+ `, isInline: true, styles: [".shk-knob{position:relative;display:inline-block}.shk-knob-svg{display:block;touch-action:none}.shk-knob-svg-focusable{cursor:pointer;outline:none}.shk-knob-svg-focusable:focus-visible{border-radius:9999px;outline:2px solid var(--shk-primary-400);outline-offset:2px}.shk-knob-disabled{opacity:.55}.shk-knob-disabled .shk-knob-svg{cursor:not-allowed}.shk-knob-label{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:1.125rem;font-weight:600;color:var(--shk-surface-800);pointer-events:none;-webkit-user-select:none;user-select:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4394
+ }
4395
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: KnobComponent, decorators: [{
4396
+ type: Component,
4397
+ args: [{ selector: 'shk-knob', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
4398
+ <div
4399
+ class="shk-knob"
4400
+ [class.shk-knob-disabled]="effectiveDisabled()"
4401
+ [style]="{ width: size() + 'px', height: size() + 'px' }"
4402
+ >
4403
+ <svg
4404
+ #knobSvg
4405
+ class="shk-knob-svg"
4406
+ [attr.width]="size()"
4407
+ [attr.height]="size()"
4408
+ [attr.viewBox]="'0 0 ' + size() + ' ' + size()"
4409
+ role="slider"
4410
+ tabindex="0"
4411
+ [attr.aria-label]="ariaLabel() || 'Valor'"
4412
+ [attr.aria-valuemin]="min()"
4413
+ [attr.aria-valuemax]="max()"
4414
+ [attr.aria-valuenow]="value()"
4415
+ [class.shk-knob-svg-focusable]="!effectiveDisabled()"
4416
+ (pointerdown)="onPointerDown($event)"
4417
+ (pointermove)="onPointerMove($event)"
4418
+ (pointerup)="onPointerUp($event)"
4419
+ (pointercancel)="onPointerUp($event)"
4420
+ (keydown.arrowup)="updateByKey(step())"
4421
+ (keydown.arrowright)="updateByKey(step())"
4422
+ (keydown.arrowdown)="updateByKey(-step())"
4423
+ (keydown.arrowleft)="updateByKey(-step())"
4424
+ >
4425
+ <path
4426
+ [attr.d]="rangeArc()"
4427
+ fill="none"
4428
+ [style.stroke]="rangeColor()"
4429
+ [attr.stroke-width]="strokeWidth()"
4430
+ stroke-linecap="round"
4431
+ ></path>
4432
+ <path
4433
+ [attr.d]="valueArc()"
4434
+ fill="none"
4435
+ [style.stroke]="valueColor()"
4436
+ [attr.stroke-width]="strokeWidth()"
4437
+ stroke-linecap="round"
4438
+ ></path>
4439
+ </svg>
4440
+ @if (showValue()) {
4441
+ <div class="shk-knob-label">{{ value() }}</div>
4442
+ }
4443
+ </div>
4444
+ `, providers: [
4445
+ {
4446
+ provide: NG_VALUE_ACCESSOR,
4447
+ useExisting: forwardRef(() => KnobComponent),
4448
+ multi: true,
4449
+ },
4450
+ ], styles: [".shk-knob{position:relative;display:inline-block}.shk-knob-svg{display:block;touch-action:none}.shk-knob-svg-focusable{cursor:pointer;outline:none}.shk-knob-svg-focusable:focus-visible{border-radius:9999px;outline:2px solid var(--shk-primary-400);outline-offset:2px}.shk-knob-disabled{opacity:.55}.shk-knob-disabled .shk-knob-svg{cursor:not-allowed}.shk-knob-label{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:1.125rem;font-weight:600;color:var(--shk-surface-800);pointer-events:none;-webkit-user-select:none;user-select:none}\n"] }]
4451
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], valueColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueColor", required: false }] }], rangeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeColor", required: false }] }], showValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValue", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], change: [{ type: i0.Output, args: ["change"] }], end: [{ type: i0.Output, args: ["end"] }], knobSvg: [{ type: i0.ViewChild, args: ['knobSvg', { isSignal: true }] }] } });
4452
+
4453
+ /** Circular indeterminate spinner. */
4454
+ class LoadingSpinnerComponent {
4455
+ constructor() {
4456
+ /** Diameter of the spinner. */
4457
+ this.size = input('2rem', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
4458
+ /** Border thickness in px. */
4459
+ this.thickness = input(3, ...(ngDevMode ? [{ debugName: "thickness" }] : /* istanbul ignore next */ []));
4460
+ /** Color of the moving arc. Defaults to the primary token. */
4461
+ this.color = input('', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
4462
+ /** Optional text shown under the spinner. */
4463
+ this.label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
4464
+ }
4465
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: LoadingSpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4466
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: LoadingSpinnerComponent, isStandalone: true, selector: "shk-loading-spinner", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, thickness: { classPropertyName: "thickness", publicName: "thickness", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4467
+ <div
4468
+ class="shk-loading-spinner"
4469
+ [style]="{
4470
+ width: size(),
4471
+ height: size(),
4472
+ }"
4473
+ role="status"
4474
+ aria-label="Cargando"
4475
+ >
4476
+ <div
4477
+ class="shk-loading-spinner-circle"
4478
+ [style]="{
4479
+ borderWidth: thickness(),
4480
+ borderTopColor: color() || undefined,
4481
+ }"
4482
+ ></div>
4483
+ </div>
4484
+ @if (label()) {
4485
+ <span class="shk-loading-spinner-label">{{ label() }}</span>
4486
+ }
4487
+ `, isInline: true, styles: [":host{display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem}.shk-loading-spinner{display:inline-block}.shk-loading-spinner-circle{width:100%;height:100%;border-radius:9999px;border-style:solid;border-color:color-mix(in srgb,var(--shk-primary-600) 20%,transparent);border-top-color:var(--shk-primary-600);animation:shk-loading-spin .8s linear infinite}.shk-loading-spinner-label{font-size:.8125rem;color:var(--shk-surface-500)}@keyframes shk-loading-spin{to{transform:rotate(360deg)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4488
+ }
4489
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: LoadingSpinnerComponent, decorators: [{
4490
+ type: Component,
4491
+ args: [{ selector: 'shk-loading-spinner', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
4492
+ <div
4493
+ class="shk-loading-spinner"
4494
+ [style]="{
4495
+ width: size(),
4496
+ height: size(),
4497
+ }"
4498
+ role="status"
4499
+ aria-label="Cargando"
4500
+ >
4501
+ <div
4502
+ class="shk-loading-spinner-circle"
4503
+ [style]="{
4504
+ borderWidth: thickness(),
4505
+ borderTopColor: color() || undefined,
4506
+ }"
4507
+ ></div>
4508
+ </div>
4509
+ @if (label()) {
4510
+ <span class="shk-loading-spinner-label">{{ label() }}</span>
4511
+ }
4512
+ `, styles: [":host{display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem}.shk-loading-spinner{display:inline-block}.shk-loading-spinner-circle{width:100%;height:100%;border-radius:9999px;border-style:solid;border-color:color-mix(in srgb,var(--shk-primary-600) 20%,transparent);border-top-color:var(--shk-primary-600);animation:shk-loading-spin .8s linear infinite}.shk-loading-spinner-label{font-size:.8125rem;color:var(--shk-surface-500)}@keyframes shk-loading-spin{to{transform:rotate(360deg)}}\n"] }]
4513
+ }], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], thickness: [{ type: i0.Input, args: [{ isSignal: true, alias: "thickness", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
4514
+
4515
+ /** Progress bar with an indeterminate sweep or a determinate percentage. */
4516
+ class LoadingBarComponent {
4517
+ constructor() {
4518
+ /** Height of the bar. */
4519
+ this.height = input('0.25rem', ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
4520
+ /** When true the bar animates without a known percentage. */
4521
+ this.indeterminate = input(true, ...(ngDevMode ? [{ debugName: "indeterminate" }] : /* istanbul ignore next */ []));
4522
+ /** Current progress 0-100 when `indeterminate` is false. */
4523
+ this.progress = input(0, ...(ngDevMode ? [{ debugName: "progress" }] : /* istanbul ignore next */ []));
4524
+ /** Fill color. Defaults to the primary token. */
4525
+ this.color = input('', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
4526
+ /** Renders fully rounded ends. */
4527
+ this.rounded = input(true, ...(ngDevMode ? [{ debugName: "rounded" }] : /* istanbul ignore next */ []));
4528
+ this.fillStyle = computed(() => {
4529
+ if (this.indeterminate())
4530
+ return {};
4531
+ return { width: `${Math.max(0, Math.min(100, this.progress()))}%` };
4532
+ }, ...(ngDevMode ? [{ debugName: "fillStyle" }] : /* istanbul ignore next */ []));
4533
+ }
4534
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: LoadingBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4535
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.18", type: LoadingBarComponent, isStandalone: true, selector: "shk-loading-bar", inputs: { height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null }, progress: { classPropertyName: "progress", publicName: "progress", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4536
+ <div
4537
+ class="shk-loading-bar"
4538
+ [class.shk-loading-bar-indeterminate]="indeterminate()"
4539
+ [style]="{ height: height(), borderRadius: rounded() ? '9999px' : undefined }"
4540
+ role="progressbar"
4541
+ [attr.aria-valuenow]="indeterminate() ? undefined : progress()"
4542
+ aria-label="Progreso"
4543
+ >
4544
+ <div
4545
+ class="shk-loading-bar-fill"
4546
+ [style]="fillStyle()"
4547
+ [style.background-color]="color() || undefined"
4548
+ [style.border-radius]="rounded() ? '9999px' : undefined"
4549
+ ></div>
4550
+ </div>
4551
+ `, isInline: true, styles: [".shk-loading-bar{width:100%;position:relative;overflow:hidden;background-color:var(--shk-surface-200)}.shk-loading-bar-fill{height:100%;background-color:var(--shk-primary-600);transition:width .3s ease-in-out}.shk-loading-bar-indeterminate .shk-loading-bar-fill{width:40%;animation:shk-loading-bar-sweep 1.2s ease-in-out infinite}@keyframes shk-loading-bar-sweep{0%{transform:translate(-100%)}to{transform:translate(350%)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4552
+ }
4553
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: LoadingBarComponent, decorators: [{
4554
+ type: Component,
4555
+ args: [{ selector: 'shk-loading-bar', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
4556
+ <div
4557
+ class="shk-loading-bar"
4558
+ [class.shk-loading-bar-indeterminate]="indeterminate()"
4559
+ [style]="{ height: height(), borderRadius: rounded() ? '9999px' : undefined }"
4560
+ role="progressbar"
4561
+ [attr.aria-valuenow]="indeterminate() ? undefined : progress()"
4562
+ aria-label="Progreso"
4563
+ >
4564
+ <div
4565
+ class="shk-loading-bar-fill"
4566
+ [style]="fillStyle()"
4567
+ [style.background-color]="color() || undefined"
4568
+ [style.border-radius]="rounded() ? '9999px' : undefined"
4569
+ ></div>
4570
+ </div>
4571
+ `, styles: [".shk-loading-bar{width:100%;position:relative;overflow:hidden;background-color:var(--shk-surface-200)}.shk-loading-bar-fill{height:100%;background-color:var(--shk-primary-600);transition:width .3s ease-in-out}.shk-loading-bar-indeterminate .shk-loading-bar-fill{width:40%;animation:shk-loading-bar-sweep 1.2s ease-in-out infinite}@keyframes shk-loading-bar-sweep{0%{transform:translate(-100%)}to{transform:translate(350%)}}\n"] }]
4572
+ }], propDecorators: { height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }], progress: [{ type: i0.Input, args: [{ isSignal: true, alias: "progress", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }] } });
4573
+
4574
+ /** Wraps content and shows a centered spinner over it while `visible` is true. */
4575
+ class LoadingOverlayComponent {
4576
+ constructor() {
4577
+ /** Controls whether the spinner layer is shown. */
4578
+ this.visible = input(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
4579
+ /** Optional text under the spinner. */
4580
+ this.message = input('', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
4581
+ /** Diameter of the spinner. */
4582
+ this.spinnerSize = input('2.5rem', ...(ngDevMode ? [{ debugName: "spinnerSize" }] : /* istanbul ignore next */ []));
4583
+ /** Spinner color. Defaults to the primary token. */
4584
+ this.color = input('', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
4585
+ /** Backdrop behind the spinner. */
4586
+ this.backdrop = input('transparent', ...(ngDevMode ? [{ debugName: "backdrop" }] : /* istanbul ignore next */ []));
4587
+ }
4588
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: LoadingOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4589
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: LoadingOverlayComponent, isStandalone: true, selector: "shk-loading-overlay", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, spinnerSize: { classPropertyName: "spinnerSize", publicName: "spinnerSize", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, backdrop: { classPropertyName: "backdrop", publicName: "backdrop", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4590
+ <div
4591
+ class="shk-loading-overlay"
4592
+ [class.shk-loading-overlay-light]="backdrop() === 'light'"
4593
+ [class.shk-loading-overlay-dark]="backdrop() === 'dark'"
4594
+ >
4595
+ <ng-content></ng-content>
4596
+ @if (visible()) {
4597
+ <div class="shk-loading-overlay-layer">
4598
+ <shk-loading-spinner [size]="spinnerSize()" [color]="color()" [label]="message()" />
4599
+ </div>
4600
+ }
4601
+ </div>
4602
+ `, isInline: true, styles: [":host{display:block}.shk-loading-overlay{position:relative;min-height:1.5rem}.shk-loading-overlay-layer{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;z-index:10}.shk-loading-overlay-light .shk-loading-overlay-layer{background-color:#ffffffa6}.dark .shk-loading-overlay-light .shk-loading-overlay-layer{background-color:#0000008c}.shk-loading-overlay-dark .shk-loading-overlay-layer{background-color:#0006}\n"], dependencies: [{ kind: "component", type: LoadingSpinnerComponent, selector: "shk-loading-spinner", inputs: ["size", "thickness", "color", "label"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4603
+ }
4604
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: LoadingOverlayComponent, decorators: [{
4605
+ type: Component,
4606
+ args: [{ selector: 'shk-loading-overlay', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [LoadingSpinnerComponent], template: `
4607
+ <div
4608
+ class="shk-loading-overlay"
4609
+ [class.shk-loading-overlay-light]="backdrop() === 'light'"
4610
+ [class.shk-loading-overlay-dark]="backdrop() === 'dark'"
4611
+ >
4612
+ <ng-content></ng-content>
4613
+ @if (visible()) {
4614
+ <div class="shk-loading-overlay-layer">
4615
+ <shk-loading-spinner [size]="spinnerSize()" [color]="color()" [label]="message()" />
4616
+ </div>
4617
+ }
4618
+ </div>
4619
+ `, styles: [":host{display:block}.shk-loading-overlay{position:relative;min-height:1.5rem}.shk-loading-overlay-layer{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;z-index:10}.shk-loading-overlay-light .shk-loading-overlay-layer{background-color:#ffffffa6}.dark .shk-loading-overlay-light .shk-loading-overlay-layer{background-color:#0000008c}.shk-loading-overlay-dark .shk-loading-overlay-layer{background-color:#0006}\n"] }]
4620
+ }], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], spinnerSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "spinnerSize", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], backdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "backdrop", required: false }] }] } });
4621
+
4622
+ /** Bouncing dots loading indicator. */
4623
+ class LoadingDotsComponent {
4624
+ constructor() {
4625
+ /** Diameter of each dot. */
4626
+ this.size = input('0.5rem', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
4627
+ /** Dot color. Defaults to the primary token. */
4628
+ this.color = input('', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
4629
+ /** Number of dots. */
4630
+ this.count = input(3, ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
4631
+ /** Optional text shown next to the dots. */
4632
+ this.label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
4633
+ this.dots = computed(() => Array.from({ length: Math.max(1, this.count()) }), ...(ngDevMode ? [{ debugName: "dots" }] : /* istanbul ignore next */ []));
4634
+ }
4635
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: LoadingDotsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4636
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: LoadingDotsComponent, isStandalone: true, selector: "shk-loading-dots", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4637
+ <span class="shk-loading-dots" role="status" aria-label="Cargando">
4638
+ @for (dot of dots(); track $index) {
4639
+ <span
4640
+ class="shk-loading-dot"
4641
+ [style]="{
4642
+ width: size(),
4643
+ height: size(),
4644
+ backgroundColor: color() || undefined,
4645
+ animationDelay: $index * 0.12 + 's',
4646
+ }"
4647
+ ></span>
4648
+ }
4649
+ </span>
4650
+ @if (label()) {
4651
+ <span class="shk-loading-dots-label">{{ label() }}</span>
4652
+ }
4653
+ `, isInline: true, styles: [":host{display:inline-flex;align-items:center;gap:.5rem}.shk-loading-dots{display:inline-flex;align-items:center;gap:.25rem}.shk-loading-dot{border-radius:9999px;background-color:var(--shk-primary-600);animation:shk-loading-dots-bounce 1.4s ease-in-out infinite}.shk-loading-dots-label{font-size:.8125rem;color:var(--shk-surface-500)}@keyframes shk-loading-dots-bounce{0%,80%,to{transform:scale(.6);opacity:.5}40%{transform:scale(1);opacity:1}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4654
+ }
4655
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: LoadingDotsComponent, decorators: [{
4656
+ type: Component,
4657
+ args: [{ selector: 'shk-loading-dots', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
4658
+ <span class="shk-loading-dots" role="status" aria-label="Cargando">
4659
+ @for (dot of dots(); track $index) {
4660
+ <span
4661
+ class="shk-loading-dot"
4662
+ [style]="{
4663
+ width: size(),
4664
+ height: size(),
4665
+ backgroundColor: color() || undefined,
4666
+ animationDelay: $index * 0.12 + 's',
4667
+ }"
4668
+ ></span>
4669
+ }
4670
+ </span>
4671
+ @if (label()) {
4672
+ <span class="shk-loading-dots-label">{{ label() }}</span>
4673
+ }
4674
+ `, styles: [":host{display:inline-flex;align-items:center;gap:.5rem}.shk-loading-dots{display:inline-flex;align-items:center;gap:.25rem}.shk-loading-dot{border-radius:9999px;background-color:var(--shk-primary-600);animation:shk-loading-dots-bounce 1.4s ease-in-out infinite}.shk-loading-dots-label{font-size:.8125rem;color:var(--shk-surface-500)}@keyframes shk-loading-dots-bounce{0%,80%,to{transform:scale(.6);opacity:.5}40%{transform:scale(1);opacity:1}}\n"] }]
4675
+ }], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
4676
+
4677
+ /** Shared state between shk-tabs and its projected tab/tabpanel children. */
4678
+ class TabsService {
4679
+ constructor() {
4680
+ this.active = signal(undefined, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
4681
+ }
4682
+ select(value) {
4683
+ this.active.set(value);
4684
+ }
4685
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TabsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4686
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TabsService }); }
4687
+ }
4688
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TabsService, decorators: [{
4689
+ type: Injectable
4690
+ }] });
4691
+
4692
+ /** Container for tab panels. Renders its projected shk-tablist/shh-tab content. */
4693
+ class TabsComponent {
4694
+ constructor() {
4695
+ /** Currently active tab value. Two-way bindable with [(value)]. */
4696
+ this.value = model(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4697
+ /** Allows the tab header to scroll horizontally when it overflows. */
4698
+ this.scrollable = input(false, ...(ngDevMode ? [{ debugName: "scrollable" }] : /* istanbul ignore next */ []));
4699
+ this.tabsSvc = inject(TabsService);
4700
+ effect(() => {
4701
+ this.tabsSvc.active.set(this.value());
4702
+ });
4703
+ effect(() => {
4704
+ const active = this.tabsSvc.active();
4705
+ if (active !== this.value()) {
4706
+ this.value.set(active);
4707
+ }
4708
+ });
4709
+ }
4710
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4711
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.18", type: TabsComponent, isStandalone: true, selector: "shk-tabs", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, scrollable: { classPropertyName: "scrollable", publicName: "scrollable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, providers: [TabsService], ngImport: i0, template: `
4712
+ <div class="shk-tabs">
4713
+ <ng-content></ng-content>
4714
+ </div>
4715
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4716
+ }
4717
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TabsComponent, decorators: [{
4718
+ type: Component,
4719
+ args: [{
4720
+ selector: 'shk-tabs',
4721
+ standalone: true,
4722
+ changeDetection: ChangeDetectionStrategy.OnPush,
4723
+ providers: [TabsService],
4724
+ template: `
4725
+ <div class="shk-tabs">
4726
+ <ng-content></ng-content>
4727
+ </div>
4728
+ `,
4729
+ }]
4730
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], scrollable: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollable", required: false }] }] } });
4731
+
4732
+ /** Horizontal bar that contains the shk-tab headers. */
4733
+ class TablistComponent {
4734
+ constructor() {
4735
+ /** Allows the header to scroll horizontally when it overflows. */
4736
+ this.scrollable = input(false, ...(ngDevMode ? [{ debugName: "scrollable" }] : /* istanbul ignore next */ []));
4737
+ /** Extra CSS classes. */
4738
+ this.styleClass = input('', ...(ngDevMode ? [{ debugName: "styleClass" }] : /* istanbul ignore next */ []));
4739
+ }
4740
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TablistComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4741
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.18", type: TablistComponent, isStandalone: true, selector: "shk-tablist", inputs: { scrollable: { classPropertyName: "scrollable", publicName: "scrollable", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4742
+ <div
4743
+ class="shk-tablist"
4744
+ [class.shk-tablist-scrollable]="scrollable()"
4745
+ [class]="styleClass()"
4746
+ role="tablist"
4747
+ aria-orientation="horizontal"
4748
+ >
4749
+ <ng-content></ng-content>
4750
+ </div>
4751
+ `, isInline: true, styles: [".shk-tablist{display:flex;align-items:center;gap:.25rem;flex-wrap:wrap;border-bottom:1px solid var(--shk-surface-200)}.dark .shk-tablist{border-bottom-color:var(--shk-surface-200)}.shk-tablist-scrollable{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:thin}.shk-tablist-scrollable::-webkit-scrollbar{height:6px}.shk-tablist-scrollable::-webkit-scrollbar-thumb{background:var(--shk-surface-300);border-radius:9999px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4752
+ }
4753
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TablistComponent, decorators: [{
4754
+ type: Component,
4755
+ args: [{ selector: 'shk-tablist', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
4756
+ <div
4757
+ class="shk-tablist"
4758
+ [class.shk-tablist-scrollable]="scrollable()"
4759
+ [class]="styleClass()"
4760
+ role="tablist"
4761
+ aria-orientation="horizontal"
4762
+ >
4763
+ <ng-content></ng-content>
4764
+ </div>
4765
+ `, styles: [".shk-tablist{display:flex;align-items:center;gap:.25rem;flex-wrap:wrap;border-bottom:1px solid var(--shk-surface-200)}.dark .shk-tablist{border-bottom-color:var(--shk-surface-200)}.shk-tablist-scrollable{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:thin}.shk-tablist-scrollable::-webkit-scrollbar{height:6px}.shk-tablist-scrollable::-webkit-scrollbar-thumb{background:var(--shk-surface-300);border-radius:9999px}\n"] }]
4766
+ }], propDecorators: { scrollable: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollable", required: false }] }], styleClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "styleClass", required: false }] }] } });
4767
+
4768
+ /** A single tab header. Clicking it activates the matching panel. */
4769
+ class TabComponent {
4770
+ constructor() {
4771
+ /** Value that links this tab to its panel. */
4772
+ this.value = input(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4773
+ /** Disables the tab. */
4774
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
4775
+ /** Extra CSS classes. */
4776
+ this.styleClass = input('', ...(ngDevMode ? [{ debugName: "styleClass" }] : /* istanbul ignore next */ []));
4777
+ this.tabsSvc = inject(TabsService);
4778
+ this.active = computed(() => this.tabsSvc.active() === this.value(), ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
4779
+ }
4780
+ select() {
4781
+ if (this.disabled())
4782
+ return;
4783
+ this.tabsSvc.select(this.value());
4784
+ }
4785
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4786
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.18", type: TabComponent, isStandalone: true, selector: "shk-tab", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4787
+ <button
4788
+ type="button"
4789
+ class="shk-tab"
4790
+ [class]="styleClass()"
4791
+ [class.shk-tab-active]="active()"
4792
+ [class.shk-tab-disabled]="disabled()"
4793
+ role="tab"
4794
+ [attr.aria-selected]="active()"
4795
+ [disabled]="disabled()"
4796
+ (click)="select()"
4797
+ >
4798
+ <ng-content></ng-content>
4799
+ </button>
4800
+ `, isInline: true, styles: [".shk-tab{display:inline-flex;align-items:center;gap:.375rem;padding:.625rem 1rem;font-size:.875rem;font-weight:500;color:var(--shk-surface-500);background:transparent;border:none;border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer;white-space:nowrap;transition:color .15s ease-in-out,border-color .15s ease-in-out,background-color .15s ease-in-out}.shk-tab:hover:not(:disabled):not(.shk-tab-active){color:var(--shk-surface-800)}.shk-tab-active{color:var(--shk-primary-600);border-bottom-color:var(--shk-primary-600);font-weight:600}.dark .shk-tab-active{color:var(--shk-primary-400);border-bottom-color:var(--shk-primary-400)}.shk-tab:disabled{opacity:.5;cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4801
+ }
4802
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TabComponent, decorators: [{
4803
+ type: Component,
4804
+ args: [{ selector: 'shk-tab', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
4805
+ <button
4806
+ type="button"
4807
+ class="shk-tab"
4808
+ [class]="styleClass()"
4809
+ [class.shk-tab-active]="active()"
4810
+ [class.shk-tab-disabled]="disabled()"
4811
+ role="tab"
4812
+ [attr.aria-selected]="active()"
4813
+ [disabled]="disabled()"
4814
+ (click)="select()"
4815
+ >
4816
+ <ng-content></ng-content>
4817
+ </button>
4818
+ `, styles: [".shk-tab{display:inline-flex;align-items:center;gap:.375rem;padding:.625rem 1rem;font-size:.875rem;font-weight:500;color:var(--shk-surface-500);background:transparent;border:none;border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer;white-space:nowrap;transition:color .15s ease-in-out,border-color .15s ease-in-out,background-color .15s ease-in-out}.shk-tab:hover:not(:disabled):not(.shk-tab-active){color:var(--shk-surface-800)}.shk-tab-active{color:var(--shk-primary-600);border-bottom-color:var(--shk-primary-600);font-weight:600}.dark .shk-tab-active{color:var(--shk-primary-400);border-bottom-color:var(--shk-primary-400)}.shk-tab:disabled{opacity:.5;cursor:not-allowed}\n"] }]
4819
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], styleClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "styleClass", required: false }] }] } });
4820
+
4821
+ /** Container that groups the shk-tabpanel content sections. */
4822
+ class TabpanelsComponent {
4823
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TabpanelsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4824
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.18", type: TabpanelsComponent, isStandalone: true, selector: "shk-tabpanels", ngImport: i0, template: `
4825
+ <div class="shk-tabpanels">
4826
+ <ng-content></ng-content>
4827
+ </div>
4828
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4829
+ }
4830
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TabpanelsComponent, decorators: [{
4831
+ type: Component,
4832
+ args: [{
4833
+ selector: 'shk-tabpanels',
4834
+ standalone: true,
4835
+ changeDetection: ChangeDetectionStrategy.OnPush,
4836
+ template: `
4837
+ <div class="shk-tabpanels">
4838
+ <ng-content></ng-content>
4839
+ </div>
4840
+ `,
4841
+ }]
4842
+ }] });
4843
+
4844
+ /** Content section shown only while its value is the active tab. */
4845
+ class TabPanelComponent {
4846
+ constructor() {
4847
+ /** Value that links this panel to its tab. */
4848
+ this.value = input(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4849
+ this.tabsSvc = inject(TabsService);
4850
+ this.active = computed(() => this.tabsSvc.active() === this.value(), ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
4851
+ }
4852
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TabPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4853
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: TabPanelComponent, isStandalone: true, selector: "shk-tabpanel", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4854
+ @if (active()) {
4855
+ <div class="shk-tabpanel" role="tabpanel">
4856
+ <ng-content></ng-content>
4857
+ </div>
4858
+ }
4859
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4860
+ }
4861
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TabPanelComponent, decorators: [{
4862
+ type: Component,
4863
+ args: [{
4864
+ selector: 'shk-tabpanel',
4865
+ standalone: true,
4866
+ changeDetection: ChangeDetectionStrategy.OnPush,
4867
+ template: `
4868
+ @if (active()) {
4869
+ <div class="shk-tabpanel" role="tabpanel">
4870
+ <ng-content></ng-content>
4871
+ </div>
4872
+ }
4873
+ `,
4874
+ }]
4875
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }] } });
4876
+
4877
+ class BreadcrumbService {
4878
+ constructor() {
4879
+ this.breadcrumbsSubject = new BehaviorSubject([]);
4880
+ this.breadcrumbs$ = this.breadcrumbsSubject.asObservable();
4881
+ }
4882
+ set(items) {
4883
+ this.breadcrumbsSubject.next(items);
4884
+ }
4885
+ clear() {
4886
+ this.breadcrumbsSubject.next([]);
4887
+ }
4888
+ getCurrent() {
4889
+ return this.breadcrumbsSubject.getValue();
4890
+ }
4891
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: BreadcrumbService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4892
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: BreadcrumbService, providedIn: 'root' }); }
4893
+ }
4894
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: BreadcrumbService, decorators: [{
4895
+ type: Injectable,
4896
+ args: [{
4897
+ providedIn: 'root',
4898
+ }]
4899
+ }] });
4900
+
4901
+ class BreadcrumbComponent {
4902
+ constructor() {
4903
+ this.breadcrumbService = inject(BreadcrumbService);
4904
+ this.items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
4905
+ this.separator = input('chevron', ...(ngDevMode ? [{ debugName: "separator" }] : /* istanbul ignore next */ []));
4906
+ this.useService = input(true, ...(ngDevMode ? [{ debugName: "useService" }] : /* istanbul ignore next */ []));
4907
+ this.serviceItems = signal([], ...(ngDevMode ? [{ debugName: "serviceItems" }] : /* istanbul ignore next */ []));
4908
+ this.resolvedItems = computed(() => {
4909
+ const staticItems = this.items();
4910
+ if (staticItems.length > 0)
4911
+ return staticItems;
4912
+ if (this.useService())
4913
+ return this.serviceItems();
4914
+ return [];
4915
+ }, ...(ngDevMode ? [{ debugName: "resolvedItems" }] : /* istanbul ignore next */ []));
4916
+ this.serviceSub = null;
4917
+ effect(() => {
4918
+ this.resolvedItems();
4919
+ });
4920
+ }
4921
+ ngOnInit() {
4922
+ if (this.useService()) {
4923
+ this.serviceSub = this.breadcrumbService.breadcrumbs$.subscribe((items) => {
4924
+ this.serviceItems.set(items);
4925
+ });
4926
+ }
4927
+ }
4928
+ ngOnDestroy() {
4929
+ this.serviceSub?.unsubscribe();
4930
+ }
4931
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: BreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4932
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: BreadcrumbComponent, isStandalone: true, selector: "shk-breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, separator: { classPropertyName: "separator", publicName: "separator", isSignal: true, isRequired: false, transformFunction: null }, useService: { classPropertyName: "useService", publicName: "useService", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4933
+ <nav class="shk-breadcrumb" aria-label="Breadcrumb">
4934
+ <ol class="shk-breadcrumb-list">
4935
+ @for (item of resolvedItems(); track $index; let last = $last) {
4936
+ <li class="shk-breadcrumb-item">
4937
+ @if (!last && item.route) {
4938
+ <a
4939
+ [routerLink]="item.route"
4940
+ class="shk-breadcrumb-link"
4941
+ [attr.aria-current]="null"
4942
+ >
4943
+ {{ item.label }}
4944
+ </a>
4945
+ } @else {
4946
+ <span
4947
+ class="shk-breadcrumb-current"
4948
+ [attr.aria-current]="last ? 'page' : null"
4949
+ >
4950
+ {{ item.label }}
4951
+ </span>
4952
+ }
4953
+ @if (!last) {
4954
+ <span class="shk-breadcrumb-separator" aria-hidden="true">
4955
+ @switch (separator()) {
4956
+ @case ('slash') {
4957
+ <span class="shk-breadcrumb-separator-text">/</span>
4958
+ }
4959
+ @case ('arrow') {
4960
+ <span class="shk-breadcrumb-separator-text">&gt;</span>
4961
+ }
4962
+ @default {
4963
+ <svg
4964
+ class="shk-breadcrumb-separator-icon"
4965
+ viewBox="0 0 24 24"
4966
+ fill="none"
4967
+ stroke="currentColor"
4968
+ stroke-width="2"
4969
+ stroke-linecap="round"
4970
+ stroke-linejoin="round"
4971
+ >
4972
+ <polyline points="9 18 15 12 9 6" />
4973
+ </svg>
4974
+ }
4975
+ }
4976
+ </span>
4977
+ }
4978
+ </li>
4979
+ }
4980
+ </ol>
4981
+ </nav>
4982
+ `, isInline: true, styles: [":host{display:block}.shk-breadcrumb-list{display:flex;align-items:center;flex-wrap:wrap;gap:.375rem;list-style:none;margin:0;padding:0;font-size:.9375rem;line-height:1.25rem}.shk-breadcrumb-item{display:inline-flex;align-items:center;gap:.375rem;min-width:0}.shk-breadcrumb-link{color:var(--shk-surface-500);text-decoration:none;transition:color .15s ease;cursor:pointer}.shk-breadcrumb-link:hover{color:var(--shk-primary-600)}.shk-breadcrumb-current{color:var(--shk-primary-700);font-weight:600}.shk-breadcrumb-separator{display:inline-flex;align-items:center;justify-content:center;color:var(--shk-surface-300);-webkit-user-select:none;user-select:none;margin:0 .25rem}.shk-breadcrumb-separator-text{font-size:.875rem}.shk-breadcrumb-separator-icon{width:.875rem;height:.875rem}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4983
+ }
4984
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: BreadcrumbComponent, decorators: [{
4985
+ type: Component,
4986
+ args: [{ selector: 'shk-breadcrumb', standalone: true, imports: [RouterLink], changeDetection: ChangeDetectionStrategy.OnPush, template: `
4987
+ <nav class="shk-breadcrumb" aria-label="Breadcrumb">
4988
+ <ol class="shk-breadcrumb-list">
4989
+ @for (item of resolvedItems(); track $index; let last = $last) {
4990
+ <li class="shk-breadcrumb-item">
4991
+ @if (!last && item.route) {
4992
+ <a
4993
+ [routerLink]="item.route"
4994
+ class="shk-breadcrumb-link"
4995
+ [attr.aria-current]="null"
4996
+ >
4997
+ {{ item.label }}
4998
+ </a>
4999
+ } @else {
5000
+ <span
5001
+ class="shk-breadcrumb-current"
5002
+ [attr.aria-current]="last ? 'page' : null"
5003
+ >
5004
+ {{ item.label }}
5005
+ </span>
5006
+ }
5007
+ @if (!last) {
5008
+ <span class="shk-breadcrumb-separator" aria-hidden="true">
5009
+ @switch (separator()) {
5010
+ @case ('slash') {
5011
+ <span class="shk-breadcrumb-separator-text">/</span>
5012
+ }
5013
+ @case ('arrow') {
5014
+ <span class="shk-breadcrumb-separator-text">&gt;</span>
5015
+ }
5016
+ @default {
5017
+ <svg
5018
+ class="shk-breadcrumb-separator-icon"
5019
+ viewBox="0 0 24 24"
5020
+ fill="none"
5021
+ stroke="currentColor"
5022
+ stroke-width="2"
5023
+ stroke-linecap="round"
5024
+ stroke-linejoin="round"
5025
+ >
5026
+ <polyline points="9 18 15 12 9 6" />
5027
+ </svg>
5028
+ }
5029
+ }
5030
+ </span>
5031
+ }
5032
+ </li>
5033
+ }
5034
+ </ol>
5035
+ </nav>
5036
+ `, styles: [":host{display:block}.shk-breadcrumb-list{display:flex;align-items:center;flex-wrap:wrap;gap:.375rem;list-style:none;margin:0;padding:0;font-size:.9375rem;line-height:1.25rem}.shk-breadcrumb-item{display:inline-flex;align-items:center;gap:.375rem;min-width:0}.shk-breadcrumb-link{color:var(--shk-surface-500);text-decoration:none;transition:color .15s ease;cursor:pointer}.shk-breadcrumb-link:hover{color:var(--shk-primary-600)}.shk-breadcrumb-current{color:var(--shk-primary-700);font-weight:600}.shk-breadcrumb-separator{display:inline-flex;align-items:center;justify-content:center;color:var(--shk-surface-300);-webkit-user-select:none;user-select:none;margin:0 .25rem}.shk-breadcrumb-separator-text{font-size:.875rem}.shk-breadcrumb-separator-icon{width:.875rem;height:.875rem}\n"] }]
5037
+ }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], separator: [{ type: i0.Input, args: [{ isSignal: true, alias: "separator", required: false }] }], useService: [{ type: i0.Input, args: [{ isSignal: true, alias: "useService", required: false }] }] } });
5038
+
3755
5039
  /* Theme — import theme.css in your global styles.css:
3756
5040
  @import '@shirkasoft/ui-components/theme.css';
3757
5041
  Override --shk-* CSS variables at :root or .dark to theme.
@@ -3761,5 +5045,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImpo
3761
5045
  * Generated bundle index. Do not edit.
3762
5046
  */
3763
5047
 
3764
- export { COLOR_PALETTE, ChartComponent, ConfirmDialogComponent, ConfirmDialogService, DEFAULT_MARKER_GRADIENTS, DEFAULT_SUPPLIER_ICON_COLORS, DEFAULT_SUPPLIER_TYPE_ICONS, DatePickerComponent, FieldsetComponent, FileUploadComponent, ModalComponent, ModalService, NotificationComponent, NotificationService, PriceInputComponent, RailSidebarComponent, SelectComponent, ShkWizardStepContent, SidebarComponent, TableComponent, TextFieldComponent, TimelineComponent, ToggleComponent, TooltipComponent, WizardComponent, readThemeColors };
5048
+ export { BreadcrumbComponent, BreadcrumbService, ButtonComponent, COLOR_PALETTE, ChartComponent, ConfirmDialogComponent, ConfirmDialogService, DEFAULT_MARKER_GRADIENTS, DEFAULT_SUPPLIER_ICON_COLORS, DEFAULT_SUPPLIER_TYPE_ICONS, DatePickerComponent, FieldsetComponent, FileUploadComponent, KnobComponent, LoadingBarComponent, LoadingDotsComponent, LoadingOverlayComponent, LoadingSpinnerComponent, ModalComponent, ModalService, NotificationComponent, NotificationService, PriceInputComponent, RailSidebarComponent, SelectComponent, ShkWizardStepContent, SidebarComponent, SkeletonComponent, TabComponent, TabPanelComponent, TableComponent, TablistComponent, TabpanelsComponent, TabsComponent, TagComponent, TextFieldComponent, TimelineComponent, ToggleComponent, TooltipComponent, WizardComponent, readThemeColors };
3765
5049
  //# sourceMappingURL=shirkasoft-ui-components.mjs.map