ngx-pk-ui 0.0.1

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.
@@ -0,0 +1,3117 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, viewChild, Component, contentChildren, signal, Injectable, inject, effect, output, computed, Input, EventEmitter, Output, ContentChild, Host, Optional, forwardRef, Inject, Directive, HostListener, ContentChildren, NgModule, ChangeDetectionStrategy, ElementRef } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { NgTemplateOutlet, NgStyle, CommonModule, NgClass } from '@angular/common';
5
+ import * as i3 from '@angular/forms';
6
+ import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
7
+ import { DomSanitizer } from '@angular/platform-browser';
8
+ import { Subject, of } from 'rxjs';
9
+ import * as i1$1 from '@angular/router';
10
+ import { RouterModule } from '@angular/router';
11
+
12
+ class PkTab {
13
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
14
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
15
+ content = viewChild.required('content');
16
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTab, deps: [], target: i0.ɵɵFactoryTarget.Component });
17
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.9", type: PkTab, isStandalone: true, selector: "pk-tab", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true, isSignal: true }], ngImport: i0, template: `<ng-template #content><ng-content /></ng-template>`, isInline: true });
18
+ }
19
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTab, decorators: [{
20
+ type: Component,
21
+ args: [{
22
+ selector: 'pk-tab',
23
+ template: `<ng-template #content><ng-content /></ng-template>`,
24
+ }]
25
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], content: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }] } });
26
+
27
+ class PkTabs {
28
+ tabs = contentChildren(PkTab, ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
29
+ activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
30
+ ngAfterContentInit() {
31
+ const firstEnabled = this.tabs().findIndex((t) => !t.disabled());
32
+ this.activeIndex.set(firstEnabled >= 0 ? firstEnabled : 0);
33
+ }
34
+ selectTab(index) {
35
+ if (!this.tabs()[index]?.disabled()) {
36
+ this.activeIndex.set(index);
37
+ }
38
+ }
39
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
40
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkTabs, isStandalone: true, selector: "pk-tabs", queries: [{ propertyName: "tabs", predicate: PkTab, isSignal: true }], ngImport: i0, template: "<div class=\"pk-tabs\">\n <ul class=\"pk-tabs__nav\" role=\"tablist\">\n @for (tab of tabs(); track $index) {\n <li\n class=\"pk-tabs__nav-item\"\n [class.pk-tabs__nav-item--active]=\"activeIndex() === $index\"\n [class.pk-tabs__nav-item--disabled]=\"tab.disabled()\"\n role=\"tab\"\n [attr.aria-selected]=\"activeIndex() === $index\"\n [attr.aria-disabled]=\"tab.disabled()\"\n (click)=\"selectTab($index)\"\n (keydown.enter)=\"selectTab($index)\"\n (keydown.space)=\"selectTab($index)\"\n tabindex=\"{{ tab.disabled() ? -1 : 0 }}\"\n >\n {{ tab.label() }}\n </li>\n }\n </ul>\n <div class=\"pk-tabs__content\" role=\"tabpanel\">\n @if (tabs()[activeIndex()]) {\n <ng-container *ngTemplateOutlet=\"tabs()[activeIndex()].content()\" />\n }\n </div>\n</div>\n", styles: [".pk-tabs__nav{display:flex;list-style:none;margin:0;padding:0;border-bottom:2px solid #e0e0e0}.pk-tabs__nav-item{padding:10px 20px;cursor:pointer;-webkit-user-select:none;user-select:none;color:#555;border-bottom:2px solid transparent;margin-bottom:-2px;transition:color .2s,border-color .2s}.pk-tabs__nav-item:hover:not(.pk-tabs__nav-item--disabled){color:var(--pk-btn-primary, #1976d2)}.pk-tabs__nav-item--active{color:var(--pk-btn-primary, #1976d2);border-bottom-color:var(--pk-btn-primary, #1976d2);font-weight:600}.pk-tabs__nav-item--disabled{color:#bdbdbd;cursor:not-allowed}.pk-tabs__content{padding:16px 0}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
41
+ }
42
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTabs, decorators: [{
43
+ type: Component,
44
+ args: [{ selector: 'pk-tabs', imports: [NgTemplateOutlet], template: "<div class=\"pk-tabs\">\n <ul class=\"pk-tabs__nav\" role=\"tablist\">\n @for (tab of tabs(); track $index) {\n <li\n class=\"pk-tabs__nav-item\"\n [class.pk-tabs__nav-item--active]=\"activeIndex() === $index\"\n [class.pk-tabs__nav-item--disabled]=\"tab.disabled()\"\n role=\"tab\"\n [attr.aria-selected]=\"activeIndex() === $index\"\n [attr.aria-disabled]=\"tab.disabled()\"\n (click)=\"selectTab($index)\"\n (keydown.enter)=\"selectTab($index)\"\n (keydown.space)=\"selectTab($index)\"\n tabindex=\"{{ tab.disabled() ? -1 : 0 }}\"\n >\n {{ tab.label() }}\n </li>\n }\n </ul>\n <div class=\"pk-tabs__content\" role=\"tabpanel\">\n @if (tabs()[activeIndex()]) {\n <ng-container *ngTemplateOutlet=\"tabs()[activeIndex()].content()\" />\n }\n </div>\n</div>\n", styles: [".pk-tabs__nav{display:flex;list-style:none;margin:0;padding:0;border-bottom:2px solid #e0e0e0}.pk-tabs__nav-item{padding:10px 20px;cursor:pointer;-webkit-user-select:none;user-select:none;color:#555;border-bottom:2px solid transparent;margin-bottom:-2px;transition:color .2s,border-color .2s}.pk-tabs__nav-item:hover:not(.pk-tabs__nav-item--disabled){color:var(--pk-btn-primary, #1976d2)}.pk-tabs__nav-item--active{color:var(--pk-btn-primary, #1976d2);border-bottom-color:var(--pk-btn-primary, #1976d2);font-weight:600}.pk-tabs__nav-item--disabled{color:#bdbdbd;cursor:not-allowed}.pk-tabs__content{padding:16px 0}\n"] }]
45
+ }], propDecorators: { tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => PkTab), { isSignal: true }] }] } });
46
+
47
+ class PkToastrService {
48
+ _counter = 0;
49
+ toasts = signal([], ...(ngDevMode ? [{ debugName: "toasts" }] : /* istanbul ignore next */ []));
50
+ show(type, message, title, duration = 4000) {
51
+ const toast = { id: ++this._counter, type, message, title, duration };
52
+ this.toasts.update((list) => [...list, toast]);
53
+ if (duration > 0) {
54
+ setTimeout(() => this.dismiss(toast.id), duration);
55
+ }
56
+ }
57
+ success(message, title, duration) {
58
+ this.show('success', message, title, duration);
59
+ }
60
+ error(message, title, duration) {
61
+ this.show('error', message, title, duration);
62
+ }
63
+ info(message, title, duration) {
64
+ this.show('info', message, title, duration);
65
+ }
66
+ warning(message, title, duration) {
67
+ this.show('warning', message, title, duration);
68
+ }
69
+ dismiss(id) {
70
+ this.toasts.update((list) => list.filter((t) => t.id !== id));
71
+ }
72
+ clear() {
73
+ this.toasts.set([]);
74
+ }
75
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkToastrService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
76
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkToastrService, providedIn: 'root' });
77
+ }
78
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkToastrService, decorators: [{
79
+ type: Injectable,
80
+ args: [{ providedIn: 'root' }]
81
+ }] });
82
+
83
+ class PkToastr {
84
+ toastr = inject(PkToastrService);
85
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkToastr, deps: [], target: i0.ɵɵFactoryTarget.Component });
86
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkToastr, isStandalone: true, selector: "pk-toastr", ngImport: i0, template: "<div class=\"pk-toastr-container\" aria-live=\"polite\" aria-atomic=\"false\">\n @for (toast of toastr.toasts(); track toast.id) {\n <div\n class=\"pk-toast pk-toast--{{ toast.type }}\"\n role=\"alert\"\n >\n @if (toast.title) {\n <strong class=\"pk-toast__title\">{{ toast.title }}</strong>\n }\n <span class=\"pk-toast__message\">{{ toast.message }}</span>\n <button\n class=\"pk-toast__close\"\n aria-label=\"Dismiss\"\n (click)=\"toastr.dismiss(toast.id)\"\n >\u2715</button>\n </div>\n }\n</div>\n", styles: [".pk-toastr-container{position:fixed;top:16px;right:16px;z-index:9999;display:flex;flex-direction:column;gap:8px;max-width:360px;pointer-events:none}.pk-toast{display:flex;align-items:flex-start;gap:8px;padding:12px 16px;border-radius:6px;box-shadow:0 4px 12px #00000026;color:#fff;pointer-events:all;animation:pk-toast-in .25s ease}@keyframes pk-toast-in{0%{opacity:0;transform:translate(40px)}to{opacity:1;transform:translate(0)}}.pk-toast--success{background:#43a047}.pk-toast--error{background:#e53935}.pk-toast--info{background:#1e88e5}.pk-toast--warning{background:#fb8c00}.pk-toast__title{display:block;font-weight:700;margin-bottom:2px}.pk-toast__message{flex:1;font-size:14px}.pk-toast__close{background:none;border:none;color:#fffc;cursor:pointer;font-size:14px;padding:0;line-height:1;margin-left:auto;flex-shrink:0}.pk-toast__close:hover{color:#fff}\n"] });
87
+ }
88
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkToastr, decorators: [{
89
+ type: Component,
90
+ args: [{ selector: 'pk-toastr', template: "<div class=\"pk-toastr-container\" aria-live=\"polite\" aria-atomic=\"false\">\n @for (toast of toastr.toasts(); track toast.id) {\n <div\n class=\"pk-toast pk-toast--{{ toast.type }}\"\n role=\"alert\"\n >\n @if (toast.title) {\n <strong class=\"pk-toast__title\">{{ toast.title }}</strong>\n }\n <span class=\"pk-toast__message\">{{ toast.message }}</span>\n <button\n class=\"pk-toast__close\"\n aria-label=\"Dismiss\"\n (click)=\"toastr.dismiss(toast.id)\"\n >\u2715</button>\n </div>\n }\n</div>\n", styles: [".pk-toastr-container{position:fixed;top:16px;right:16px;z-index:9999;display:flex;flex-direction:column;gap:8px;max-width:360px;pointer-events:none}.pk-toast{display:flex;align-items:flex-start;gap:8px;padding:12px 16px;border-radius:6px;box-shadow:0 4px 12px #00000026;color:#fff;pointer-events:all;animation:pk-toast-in .25s ease}@keyframes pk-toast-in{0%{opacity:0;transform:translate(40px)}to{opacity:1;transform:translate(0)}}.pk-toast--success{background:#43a047}.pk-toast--error{background:#e53935}.pk-toast--info{background:#1e88e5}.pk-toast--warning{background:#fb8c00}.pk-toast__title{display:block;font-weight:700;margin-bottom:2px}.pk-toast__message{flex:1;font-size:14px}.pk-toast__close{background:none;border:none;color:#fffc;cursor:pointer;font-size:14px;padding:0;line-height:1;margin-left:auto;flex-shrink:0}.pk-toast__close:hover{color:#fff}\n"] }]
91
+ }] });
92
+
93
+ class PkAlertService {
94
+ /** The component reads this signal to know what (if anything) to display. */
95
+ slot = signal(null, ...(ngDevMode ? [{ debugName: "slot" }] : /* istanbul ignore next */ []));
96
+ _open(config) {
97
+ return new Promise((resolve) => {
98
+ this.slot.set({ config, resolve });
99
+ });
100
+ }
101
+ success(message, title = 'Success') {
102
+ return this._open({ type: 'success', title, message });
103
+ }
104
+ warn(message, title = 'Warning') {
105
+ return this._open({ type: 'warn', title, message });
106
+ }
107
+ error(message, title = 'Error') {
108
+ return this._open({ type: 'error', title, message });
109
+ }
110
+ confirm(message, title = 'Confirm', confirmLabel = 'Yes', cancelLabel = 'No') {
111
+ return this._open({
112
+ type: 'confirm',
113
+ title,
114
+ message,
115
+ confirmLabel,
116
+ cancelLabel,
117
+ });
118
+ }
119
+ input(message, inputType = 'string', title = 'Input', defaultValue) {
120
+ return this._open({
121
+ type: 'input',
122
+ title,
123
+ message,
124
+ inputType,
125
+ defaultValue,
126
+ });
127
+ }
128
+ /** Called by the component after the user makes a choice. */
129
+ _resolve(value) {
130
+ const current = this.slot();
131
+ if (current) {
132
+ this.slot.set(null);
133
+ current.resolve(value);
134
+ }
135
+ }
136
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkAlertService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
137
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkAlertService, providedIn: 'root' });
138
+ }
139
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkAlertService, decorators: [{
140
+ type: Injectable,
141
+ args: [{ providedIn: 'root' }]
142
+ }] });
143
+
144
+ class PkAlert {
145
+ svc = inject(PkAlertService);
146
+ /** Local copy of input value while the dialog is open */
147
+ inputValue = signal(null, ...(ngDevMode ? [{ debugName: "inputValue" }] : /* istanbul ignore next */ []));
148
+ constructor() {
149
+ effect(() => {
150
+ const slot = this.svc.slot();
151
+ if (slot) {
152
+ const def = slot.config.defaultValue;
153
+ this.inputValue.set(def !== undefined ? def : this._defaultFor(slot.config.inputType));
154
+ }
155
+ });
156
+ }
157
+ confirm() {
158
+ const slot = this.svc.slot();
159
+ if (!slot)
160
+ return;
161
+ const type = slot.config.type;
162
+ if (type === 'confirm') {
163
+ this.svc._resolve(true);
164
+ }
165
+ else if (type === 'input') {
166
+ this.svc._resolve(this.inputValue());
167
+ }
168
+ else {
169
+ this.svc._resolve(undefined);
170
+ }
171
+ }
172
+ cancel() {
173
+ const slot = this.svc.slot();
174
+ if (!slot)
175
+ return;
176
+ const type = slot.config.type;
177
+ this.svc._resolve(type === 'confirm' ? false : null);
178
+ }
179
+ _defaultFor(inputType) {
180
+ switch (inputType) {
181
+ case 'number': return 0;
182
+ case 'boolean': return false;
183
+ case 'date': return '';
184
+ default: return '';
185
+ }
186
+ }
187
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkAlert, deps: [], target: i0.ɵɵFactoryTarget.Component });
188
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkAlert, isStandalone: true, selector: "pk-alert", ngImport: i0, template: "@if (svc.slot(); as slot) {\n <div class=\"pk-alert-backdrop\" (click)=\"cancel()\">\n <div class=\"pk-alert-card pk-alert-card--{{ slot.config.type }}\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\">\n\n <div class=\"pk-alert-icon\">\n @switch (slot.config.type) {\n @case ('success') { \u2705 }\n @case ('warn') { \u26A0\uFE0F }\n @case ('error') { \u274C }\n @case ('confirm') { \uD83E\uDD14 }\n @case ('input') { \u270F\uFE0F }\n }\n </div>\n\n @if (slot.config.title) {\n <h2 class=\"pk-alert-title\">{{ slot.config.title }}</h2>\n }\n\n <p class=\"pk-alert-message\">{{ slot.config.message }}</p>\n\n @if (slot.config.type === 'input') {\n <div class=\"pk-alert-input-wrap\">\n @switch (slot.config.inputType) {\n @case ('boolean') {\n <label class=\"pk-alert-toggle\">\n <input type=\"checkbox\" [checked]=\"!!inputValue()\"\n (change)=\"inputValue.set($any($event.target).checked)\" />\n <span>{{ inputValue() ? 'Yes' : 'No' }}</span>\n </label>\n }\n @case ('number') {\n <input class=\"pk-alert-field\" type=\"number\"\n [value]=\"inputValue()\"\n (input)=\"inputValue.set(+$any($event.target).value)\"\n autofocus />\n }\n @case ('date') {\n <input class=\"pk-alert-field\" type=\"date\"\n [value]=\"inputValue()\"\n (input)=\"inputValue.set($any($event.target).value)\"\n autofocus />\n }\n @default {\n <input class=\"pk-alert-field\" type=\"text\"\n [value]=\"inputValue()\"\n (input)=\"inputValue.set($any($event.target).value)\"\n autofocus />\n }\n }\n </div>\n }\n\n <div class=\"pk-alert-actions\">\n @if (slot.config.type === 'confirm' || slot.config.type === 'input') {\n <button class=\"pk-alert-btn pk-alert-btn--cancel\" (click)=\"cancel()\">\n {{ slot.config.cancelLabel ?? 'Cancel' }}\n </button>\n }\n <button class=\"pk-alert-btn pk-alert-btn--confirm pk-alert-btn--{{ slot.config.type }}\"\n (click)=\"confirm()\">\n {{ slot.config.confirmLabel ?? 'OK' }}\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [".pk-alert-backdrop{position:fixed;inset:0;background:#00000073;display:flex;align-items:center;justify-content:center;z-index:10000;animation:pk-backdrop-in .15s ease}@keyframes pk-backdrop-in{0%{opacity:0}to{opacity:1}}.pk-alert-card{background:#fff;border-radius:10px;padding:32px 28px 24px;min-width:320px;max-width:480px;width:90vw;box-shadow:0 8px 32px #0003;text-align:center;animation:pk-card-in .2s ease}@keyframes pk-card-in{0%{opacity:0;transform:scale(.92) translateY(-12px)}to{opacity:1;transform:scale(1) translateY(0)}}.pk-alert-icon{font-size:40px;line-height:1;margin-bottom:12px}.pk-alert-title{margin:0 0 8px;font-size:18px;font-weight:700;color:#222}.pk-alert-message{margin:0 0 20px;font-size:15px;color:#555;line-height:1.5}.pk-alert-input-wrap{margin-bottom:20px}.pk-alert-field{width:100%;padding:9px 12px;border:1px solid #ccc;border-radius:6px;font-size:15px;outline:none;transition:border-color .15s;box-sizing:border-box}.pk-alert-field:focus{border-color:#1976d2;box-shadow:0 0 0 2px #1976d22e}.pk-alert-toggle{display:inline-flex;align-items:center;gap:10px;font-size:15px;cursor:pointer}.pk-alert-toggle input[type=checkbox]{width:18px;height:18px;cursor:pointer}.pk-alert-actions{display:flex;justify-content:center;gap:12px}.pk-alert-btn{padding:9px 24px;border:none;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer;transition:opacity .15s}.pk-alert-btn:hover{opacity:.85}.pk-alert-btn--cancel{background:#f0f0f0;color:#444}.pk-alert-btn--confirm{color:#fff}.pk-alert-btn--confirm.pk-alert-btn--success{background:#43a047}.pk-alert-btn--confirm.pk-alert-btn--warn{background:#fb8c00}.pk-alert-btn--confirm.pk-alert-btn--error{background:#e53935}.pk-alert-btn--confirm.pk-alert-btn--confirm,.pk-alert-btn--confirm.pk-alert-btn--input{background:#1976d2}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }] });
189
+ }
190
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkAlert, decorators: [{
191
+ type: Component,
192
+ args: [{ selector: 'pk-alert', imports: [FormsModule], template: "@if (svc.slot(); as slot) {\n <div class=\"pk-alert-backdrop\" (click)=\"cancel()\">\n <div class=\"pk-alert-card pk-alert-card--{{ slot.config.type }}\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\">\n\n <div class=\"pk-alert-icon\">\n @switch (slot.config.type) {\n @case ('success') { \u2705 }\n @case ('warn') { \u26A0\uFE0F }\n @case ('error') { \u274C }\n @case ('confirm') { \uD83E\uDD14 }\n @case ('input') { \u270F\uFE0F }\n }\n </div>\n\n @if (slot.config.title) {\n <h2 class=\"pk-alert-title\">{{ slot.config.title }}</h2>\n }\n\n <p class=\"pk-alert-message\">{{ slot.config.message }}</p>\n\n @if (slot.config.type === 'input') {\n <div class=\"pk-alert-input-wrap\">\n @switch (slot.config.inputType) {\n @case ('boolean') {\n <label class=\"pk-alert-toggle\">\n <input type=\"checkbox\" [checked]=\"!!inputValue()\"\n (change)=\"inputValue.set($any($event.target).checked)\" />\n <span>{{ inputValue() ? 'Yes' : 'No' }}</span>\n </label>\n }\n @case ('number') {\n <input class=\"pk-alert-field\" type=\"number\"\n [value]=\"inputValue()\"\n (input)=\"inputValue.set(+$any($event.target).value)\"\n autofocus />\n }\n @case ('date') {\n <input class=\"pk-alert-field\" type=\"date\"\n [value]=\"inputValue()\"\n (input)=\"inputValue.set($any($event.target).value)\"\n autofocus />\n }\n @default {\n <input class=\"pk-alert-field\" type=\"text\"\n [value]=\"inputValue()\"\n (input)=\"inputValue.set($any($event.target).value)\"\n autofocus />\n }\n }\n </div>\n }\n\n <div class=\"pk-alert-actions\">\n @if (slot.config.type === 'confirm' || slot.config.type === 'input') {\n <button class=\"pk-alert-btn pk-alert-btn--cancel\" (click)=\"cancel()\">\n {{ slot.config.cancelLabel ?? 'Cancel' }}\n </button>\n }\n <button class=\"pk-alert-btn pk-alert-btn--confirm pk-alert-btn--{{ slot.config.type }}\"\n (click)=\"confirm()\">\n {{ slot.config.confirmLabel ?? 'OK' }}\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [".pk-alert-backdrop{position:fixed;inset:0;background:#00000073;display:flex;align-items:center;justify-content:center;z-index:10000;animation:pk-backdrop-in .15s ease}@keyframes pk-backdrop-in{0%{opacity:0}to{opacity:1}}.pk-alert-card{background:#fff;border-radius:10px;padding:32px 28px 24px;min-width:320px;max-width:480px;width:90vw;box-shadow:0 8px 32px #0003;text-align:center;animation:pk-card-in .2s ease}@keyframes pk-card-in{0%{opacity:0;transform:scale(.92) translateY(-12px)}to{opacity:1;transform:scale(1) translateY(0)}}.pk-alert-icon{font-size:40px;line-height:1;margin-bottom:12px}.pk-alert-title{margin:0 0 8px;font-size:18px;font-weight:700;color:#222}.pk-alert-message{margin:0 0 20px;font-size:15px;color:#555;line-height:1.5}.pk-alert-input-wrap{margin-bottom:20px}.pk-alert-field{width:100%;padding:9px 12px;border:1px solid #ccc;border-radius:6px;font-size:15px;outline:none;transition:border-color .15s;box-sizing:border-box}.pk-alert-field:focus{border-color:#1976d2;box-shadow:0 0 0 2px #1976d22e}.pk-alert-toggle{display:inline-flex;align-items:center;gap:10px;font-size:15px;cursor:pointer}.pk-alert-toggle input[type=checkbox]{width:18px;height:18px;cursor:pointer}.pk-alert-actions{display:flex;justify-content:center;gap:12px}.pk-alert-btn{padding:9px 24px;border:none;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer;transition:opacity .15s}.pk-alert-btn:hover{opacity:.85}.pk-alert-btn--cancel{background:#f0f0f0;color:#444}.pk-alert-btn--confirm{color:#fff}.pk-alert-btn--confirm.pk-alert-btn--success{background:#43a047}.pk-alert-btn--confirm.pk-alert-btn--warn{background:#fb8c00}.pk-alert-btn--confirm.pk-alert-btn--error{background:#e53935}.pk-alert-btn--confirm.pk-alert-btn--confirm,.pk-alert-btn--confirm.pk-alert-btn--input{background:#1976d2}\n"] }]
193
+ }], ctorParameters: () => [] });
194
+
195
+ class PkModalHeader {
196
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkModalHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
197
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: PkModalHeader, isStandalone: true, selector: "pk-modal-header", ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:block;padding:16px 48px 16px 20px;border-bottom:1px solid #e0e0e0;font-size:17px;font-weight:600;color:#1a1a2e;line-height:1.4}\n"] });
198
+ }
199
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkModalHeader, decorators: [{
200
+ type: Component,
201
+ args: [{ selector: 'pk-modal-header', standalone: true, template: `<ng-content />`, styles: [":host{display:block;padding:16px 48px 16px 20px;border-bottom:1px solid #e0e0e0;font-size:17px;font-weight:600;color:#1a1a2e;line-height:1.4}\n"] }]
202
+ }] });
203
+
204
+ class PkModalBody {
205
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkModalBody, deps: [], target: i0.ɵɵFactoryTarget.Component });
206
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: PkModalBody, isStandalone: true, selector: "pk-modal-body", ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:block;padding:20px;flex:1;overflow-y:auto;color:#333;line-height:1.6;font-size:14px}\n"] });
207
+ }
208
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkModalBody, decorators: [{
209
+ type: Component,
210
+ args: [{ selector: 'pk-modal-body', standalone: true, template: `<ng-content />`, styles: [":host{display:block;padding:20px;flex:1;overflow-y:auto;color:#333;line-height:1.6;font-size:14px}\n"] }]
211
+ }] });
212
+
213
+ class PkModalFooter {
214
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkModalFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
215
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: PkModalFooter, isStandalone: true, selector: "pk-modal-footer", ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 20px;border-top:1px solid #e0e0e0;background:#f9f9f9;border-bottom-left-radius:10px;border-bottom-right-radius:10px}\n"] });
216
+ }
217
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkModalFooter, decorators: [{
218
+ type: Component,
219
+ args: [{ selector: 'pk-modal-footer', standalone: true, template: `<ng-content />`, styles: [":host{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 20px;border-top:1px solid #e0e0e0;background:#f9f9f9;border-bottom-left-radius:10px;border-bottom-right-radius:10px}\n"] }]
220
+ }] });
221
+
222
+ class PkModal {
223
+ openModal = input(false, ...(ngDevMode ? [{ debugName: "openModal" }] : /* istanbul ignore next */ []));
224
+ customStyle = input(null, ...(ngDevMode ? [{ debugName: "customStyle" }] : /* istanbul ignore next */ []));
225
+ customClass = input(null, ...(ngDevMode ? [{ debugName: "customClass" }] : /* istanbul ignore next */ []));
226
+ blur = input(true, ...(ngDevMode ? [{ debugName: "blur" }] : /* istanbul ignore next */ []));
227
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
228
+ theme = input('white', ...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
229
+ closeAny = input(false, ...(ngDevMode ? [{ debugName: "closeAny" }] : /* istanbul ignore next */ []));
230
+ closeMarker = input(true, ...(ngDevMode ? [{ debugName: "closeMarker" }] : /* istanbul ignore next */ []));
231
+ onClose = output();
232
+ dialogClass = computed(() => {
233
+ const classes = [
234
+ 'pk-modal-dialog',
235
+ `pk-modal-dialog--${this.size()}`,
236
+ `pk-modal-dialog--theme-${this.theme()}`,
237
+ ];
238
+ const custom = this.customClass();
239
+ if (custom)
240
+ classes.push(custom);
241
+ return classes.join(' ');
242
+ }, ...(ngDevMode ? [{ debugName: "dialogClass" }] : /* istanbul ignore next */ []));
243
+ close() {
244
+ this.onClose.emit();
245
+ }
246
+ onOverlayClick() {
247
+ if (this.closeAny()) {
248
+ this.close();
249
+ }
250
+ }
251
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
252
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkModal, isStandalone: true, selector: "pk-modal", inputs: { openModal: { classPropertyName: "openModal", publicName: "openModal", isSignal: true, isRequired: false, transformFunction: null }, customStyle: { classPropertyName: "customStyle", publicName: "customStyle", isSignal: true, isRequired: false, transformFunction: null }, customClass: { classPropertyName: "customClass", publicName: "customClass", isSignal: true, isRequired: false, transformFunction: null }, blur: { classPropertyName: "blur", publicName: "blur", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, closeAny: { classPropertyName: "closeAny", publicName: "closeAny", isSignal: true, isRequired: false, transformFunction: null }, closeMarker: { classPropertyName: "closeMarker", publicName: "closeMarker", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClose: "onClose" }, ngImport: i0, template: "@if (openModal()) {\n <div\n class=\"pk-modal-overlay\"\n [class.pk-modal-overlay--blur]=\"blur()\"\n (click)=\"onOverlayClick()\"\n >\n <div\n [class]=\"dialogClass()\"\n [ngStyle]=\"customStyle()\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (closeMarker()) {\n <button\n class=\"pk-modal-close-btn\"\n type=\"button\"\n aria-label=\"Close modal\"\n (click)=\"close()\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n }\n\n <ng-content select=\"pk-modal-header\" />\n <ng-content select=\"pk-modal-body\" />\n <ng-content select=\"pk-modal-footer\" />\n <!-- fallback for unslotted content -->\n <ng-content />\n </div>\n </div>\n}\n", styles: [".pk-modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000;padding:16px;animation:pkModalFadeIn .18s ease}.pk-modal-overlay--blur{backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.pk-modal-dialog{background:#fff;border:1px solid #eceff3;border-radius:10px;box-shadow:0 20px 60px #00000038,0 4px 16px #0000001a;position:relative;display:flex;flex-direction:column;width:100%;max-height:calc(100vh - 32px);animation:pkModalSlideIn .18s ease;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.pk-modal-dialog:before{content:\"\";position:absolute;top:0;left:0;right:0;height:5px;border-radius:10px 10px 0 0;background:transparent;pointer-events:none}.pk-modal-dialog--xs{max-width:280px}.pk-modal-dialog--sm{max-width:360px}.pk-modal-dialog--md{max-width:520px}.pk-modal-dialog--lg{max-width:760px}.pk-modal-dialog--xl{max-width:1020px}.pk-modal-dialog--2xl{max-width:1280px}.pk-modal-dialog--full{max-width:100%;height:calc(100vh - 32px);border-radius:0}.pk-modal-dialog--full:before{border-radius:0}.pk-modal-dialog--theme-white,.pk-modal-dialog--theme-none{background:#fff;border-color:#eceff3}.pk-modal-dialog--theme-success{background:linear-gradient(180deg,#f6fff9,#fff 22%);border-color:#b7e8cb;box-shadow:0 22px 60px #00000029,0 4px 14px #16a34a29}.pk-modal-dialog--theme-success:before{background:linear-gradient(90deg,#16a34a,#22c55e)}.pk-modal-dialog--theme-warn{background:linear-gradient(180deg,#fffdf5,#fff 22%);border-color:#f7d9a6;box-shadow:0 22px 60px #00000029,0 4px 14px #f59e0b2b}.pk-modal-dialog--theme-warn:before{background:linear-gradient(90deg,#f59e0b,#fbbf24)}.pk-modal-dialog--theme-error{background:linear-gradient(180deg,#fff8f8,#fff 22%);border-color:#f5b6b6;box-shadow:0 22px 60px #00000029,0 4px 14px #dc262629}.pk-modal-dialog--theme-error:before{background:linear-gradient(90deg,#dc2626,#ef4444)}.pk-modal-dialog--theme-sky{background:linear-gradient(180deg,#f4fbff,#fff 22%);border-color:#b8e4fd;box-shadow:0 22px 60px #00000029,0 4px 14px #0ea5e92b}.pk-modal-dialog--theme-sky:before{background:linear-gradient(90deg,#0ea5e9,#38bdf8)}.pk-modal-dialog--theme-blue{background:linear-gradient(180deg,#f5f8ff,#fff 22%);border-color:#b7c9fa;box-shadow:0 22px 60px #00000029,0 4px 14px #2563eb2b}.pk-modal-dialog--theme-blue:before{background:linear-gradient(90deg,#2563eb,#3b82f6)}.pk-modal-dialog--theme-gray{background:linear-gradient(180deg,#f8fafc,#fff 22%);border-color:#d2d8e1;box-shadow:0 22px 60px #00000029,0 4px 14px #47556929}.pk-modal-dialog--theme-gray:before{background:linear-gradient(90deg,#475569,#64748b)}.pk-modal-close-btn{position:absolute;top:12px;right:12px;width:32px;height:32px;padding:0;background:transparent;border:none;border-radius:6px;cursor:pointer;color:#999;display:flex;align-items:center;justify-content:center;z-index:1;transition:background .15s,color .15s}.pk-modal-close-btn:hover{background:#f0f0f0;color:#333}.pk-modal-close-btn:active{background:#e0e0e0}@keyframes pkModalFadeIn{0%{opacity:0}to{opacity:1}}@keyframes pkModalSlideIn{0%{opacity:0;transform:scale(.96) translateY(-8px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
253
+ }
254
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkModal, decorators: [{
255
+ type: Component,
256
+ args: [{ selector: 'pk-modal', imports: [NgStyle], template: "@if (openModal()) {\n <div\n class=\"pk-modal-overlay\"\n [class.pk-modal-overlay--blur]=\"blur()\"\n (click)=\"onOverlayClick()\"\n >\n <div\n [class]=\"dialogClass()\"\n [ngStyle]=\"customStyle()\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (closeMarker()) {\n <button\n class=\"pk-modal-close-btn\"\n type=\"button\"\n aria-label=\"Close modal\"\n (click)=\"close()\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n }\n\n <ng-content select=\"pk-modal-header\" />\n <ng-content select=\"pk-modal-body\" />\n <ng-content select=\"pk-modal-footer\" />\n <!-- fallback for unslotted content -->\n <ng-content />\n </div>\n </div>\n}\n", styles: [".pk-modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000;padding:16px;animation:pkModalFadeIn .18s ease}.pk-modal-overlay--blur{backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.pk-modal-dialog{background:#fff;border:1px solid #eceff3;border-radius:10px;box-shadow:0 20px 60px #00000038,0 4px 16px #0000001a;position:relative;display:flex;flex-direction:column;width:100%;max-height:calc(100vh - 32px);animation:pkModalSlideIn .18s ease;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.pk-modal-dialog:before{content:\"\";position:absolute;top:0;left:0;right:0;height:5px;border-radius:10px 10px 0 0;background:transparent;pointer-events:none}.pk-modal-dialog--xs{max-width:280px}.pk-modal-dialog--sm{max-width:360px}.pk-modal-dialog--md{max-width:520px}.pk-modal-dialog--lg{max-width:760px}.pk-modal-dialog--xl{max-width:1020px}.pk-modal-dialog--2xl{max-width:1280px}.pk-modal-dialog--full{max-width:100%;height:calc(100vh - 32px);border-radius:0}.pk-modal-dialog--full:before{border-radius:0}.pk-modal-dialog--theme-white,.pk-modal-dialog--theme-none{background:#fff;border-color:#eceff3}.pk-modal-dialog--theme-success{background:linear-gradient(180deg,#f6fff9,#fff 22%);border-color:#b7e8cb;box-shadow:0 22px 60px #00000029,0 4px 14px #16a34a29}.pk-modal-dialog--theme-success:before{background:linear-gradient(90deg,#16a34a,#22c55e)}.pk-modal-dialog--theme-warn{background:linear-gradient(180deg,#fffdf5,#fff 22%);border-color:#f7d9a6;box-shadow:0 22px 60px #00000029,0 4px 14px #f59e0b2b}.pk-modal-dialog--theme-warn:before{background:linear-gradient(90deg,#f59e0b,#fbbf24)}.pk-modal-dialog--theme-error{background:linear-gradient(180deg,#fff8f8,#fff 22%);border-color:#f5b6b6;box-shadow:0 22px 60px #00000029,0 4px 14px #dc262629}.pk-modal-dialog--theme-error:before{background:linear-gradient(90deg,#dc2626,#ef4444)}.pk-modal-dialog--theme-sky{background:linear-gradient(180deg,#f4fbff,#fff 22%);border-color:#b8e4fd;box-shadow:0 22px 60px #00000029,0 4px 14px #0ea5e92b}.pk-modal-dialog--theme-sky:before{background:linear-gradient(90deg,#0ea5e9,#38bdf8)}.pk-modal-dialog--theme-blue{background:linear-gradient(180deg,#f5f8ff,#fff 22%);border-color:#b7c9fa;box-shadow:0 22px 60px #00000029,0 4px 14px #2563eb2b}.pk-modal-dialog--theme-blue:before{background:linear-gradient(90deg,#2563eb,#3b82f6)}.pk-modal-dialog--theme-gray{background:linear-gradient(180deg,#f8fafc,#fff 22%);border-color:#d2d8e1;box-shadow:0 22px 60px #00000029,0 4px 14px #47556929}.pk-modal-dialog--theme-gray:before{background:linear-gradient(90deg,#475569,#64748b)}.pk-modal-close-btn{position:absolute;top:12px;right:12px;width:32px;height:32px;padding:0;background:transparent;border:none;border-radius:6px;cursor:pointer;color:#999;display:flex;align-items:center;justify-content:center;z-index:1;transition:background .15s,color .15s}.pk-modal-close-btn:hover{background:#f0f0f0;color:#333}.pk-modal-close-btn:active{background:#e0e0e0}@keyframes pkModalFadeIn{0%{opacity:0}to{opacity:1}}@keyframes pkModalSlideIn{0%{opacity:0;transform:scale(.96) translateY(-8px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"] }]
257
+ }], propDecorators: { openModal: [{ type: i0.Input, args: [{ isSignal: true, alias: "openModal", required: false }] }], customStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "customStyle", required: false }] }], customClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "customClass", required: false }] }], blur: [{ type: i0.Input, args: [{ isSignal: true, alias: "blur", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], closeAny: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAny", required: false }] }], closeMarker: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeMarker", required: false }] }], onClose: [{ type: i0.Output, args: ["onClose"] }] } });
258
+
259
+ const PK_ICONS = {
260
+ // ── Navigation & UI ──────────────────────────────────────────────────────
261
+ 'search': `<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>`,
262
+ 'menu': `<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>`,
263
+ 'close': `<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>`,
264
+ 'sort': `<line x1="3" y1="6" x2="21" y2="6"/><line x1="6" y1="12" x2="18" y2="12"/><line x1="9" y1="18" x2="15" y2="18"/>`,
265
+ 'plus': `<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>`,
266
+ 'list': `<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>`,
267
+ 'reload': `<polyline points="1 4 1 10 7 10"/><polyline points="23 20 23 14 17 14"/><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4-4.64 4.36A9 9 0 0 1 3.51 15"/>`,
268
+ // ── Users & Auth ─────────────────────────────────────────────────────────
269
+ 'user': `<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>`,
270
+ 'users': `<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>`,
271
+ 'profile': `<rect x="2" y="3" width="20" height="18" rx="2"/><circle cx="12" cy="10" r="3"/><path d="M7 20v-1a5 5 0 0 1 10 0v1"/>`,
272
+ 'login': `<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/><polyline points="10 17 15 12 10 7"/><line x1="15" y1="12" x2="3" y2="12"/>`,
273
+ 'logout': `<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>`,
274
+ // ── Location ─────────────────────────────────────────────────────────────
275
+ 'map': `<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/>`,
276
+ 'map-point': `<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/>`,
277
+ // ── Files & Documents ────────────────────────────────────────────────────
278
+ 'folder-close': `<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>`,
279
+ 'folder-open': `<path d="M5 19a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v1"/><path d="M3 11h18l-1.5 8H4.5z"/>`,
280
+ 'document': `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>`,
281
+ 'report': `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="15" x2="9" y2="18"/><line x1="12" y1="12" x2="12" y2="18"/><line x1="15" y1="13" x2="15" y2="18"/>`,
282
+ 'csv': `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="12" x2="16" y2="12"/><line x1="8" y1="15" x2="16" y2="15"/><line x1="8" y1="18" x2="16" y2="18"/><line x1="12" y1="10" x2="12" y2="20"/>`,
283
+ 'xls': `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><rect x="8" y="11" width="8" height="7"/><line x1="12" y1="11" x2="12" y2="18"/><line x1="8" y1="14.5" x2="16" y2="14.5"/>`,
284
+ 'pdf': `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="12" x2="16" y2="12"/><line x1="8" y1="15" x2="16" y2="15"/><line x1="8" y1="18" x2="12" y2="18"/>`,
285
+ 'text': `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="11" x2="16" y2="11"/><line x1="8" y1="14" x2="16" y2="14"/><line x1="8" y1="17" x2="14" y2="17"/>`,
286
+ // ── Charts & Analytics ───────────────────────────────────────────────────
287
+ 'chart-pie': `<path d="M21.21 15.89A10 10 0 1 1 8 2.83"/><path d="M22 12A10 10 0 0 0 12 2v10z"/>`,
288
+ 'chart-bar': `<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/><line x1="2" y1="20" x2="22" y2="20"/>`,
289
+ 'dashboard': `<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/>`,
290
+ 'venn': `<circle cx="9" cy="12" r="7"/><circle cx="15" cy="12" r="7"/>`,
291
+ // ── System & Infrastructure ──────────────────────────────────────────────
292
+ 'database': `<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>`,
293
+ 'cog': `<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>`,
294
+ 'setting': `<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/>`,
295
+ 'server': `<rect x="2" y="2" width="20" height="8" rx="2" ry="2"/><rect x="2" y="14" width="20" height="8" rx="2" ry="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/>`,
296
+ 'shield': `<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>`,
297
+ // ── Communication ────────────────────────────────────────────────────────
298
+ 'email': `<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/>`,
299
+ 'phone': `<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 1.18h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L7.91 9a16 16 0 0 0 6 6l.92-.92a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/>`,
300
+ // ── Transfer ─────────────────────────────────────────────────────────────
301
+ 'upload': `<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>`,
302
+ 'download': `<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>`,
303
+ 'export': `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="12" x2="15" y2="12"/><polyline points="12.5 9.5 15 12 12.5 14.5"/>`,
304
+ 'import': `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="15" y1="12" x2="9" y2="12"/><polyline points="11.5 9.5 9 12 11.5 14.5"/>`,
305
+ // ── Links ────────────────────────────────────────────────────────────────
306
+ 'link': `<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>`,
307
+ 'unlink': `<path d="M18.84 12.25l1.72-1.71h-.02a5.004 5.004 0 0 0-.12-7.07 5.004 5.004 0 0 0-7.07.12l-1.72 1.71"/><path d="M5.17 11.75l-1.72 1.71a5.004 5.004 0 0 0 .12 7.07 5.004 5.004 0 0 0 7.07-.12l1.71-1.71"/><line x1="8" y1="2" x2="8" y2="5"/><line x1="2" y1="8" x2="5" y2="8"/><line x1="16" y1="19" x2="16" y2="22"/><line x1="19" y1="16" x2="22" y2="16"/>`,
308
+ // ── Editing & Actions ────────────────────────────────────────────────────
309
+ 'pencil': `<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>`,
310
+ 'save': `<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/>`,
311
+ 'trash': `<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/>`,
312
+ 'eye': `<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>`,
313
+ 'eye-off': `<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/>`,
314
+ 'print': `<polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/>`,
315
+ // ── Status ───────────────────────────────────────────────────────────────
316
+ 'check-mark': `<polyline points="20 6 9 17 4 12"/>`,
317
+ 'check-mark-circle': `<circle cx="12" cy="12" r="10"/><polyline points="9 12 11 14 15 10"/>`,
318
+ 'success': `<circle cx="12" cy="12" r="10"/><path d="M9 12l2 2 4-4"/>`,
319
+ 'warning': `<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>`,
320
+ 'error': `<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>`,
321
+ 'question': `<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/>`,
322
+ // ── Time ─────────────────────────────────────────────────────────────────
323
+ 'clock': `<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>`,
324
+ 'calendar': `<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>`,
325
+ 'time': `<circle cx="12" cy="13" r="8"/><polyline points="12 9 12 13 15 13"/><line x1="9" y1="3" x2="15" y2="3"/><line x1="12" y1="3" x2="12" y2="5"/>`,
326
+ 'wait': `<path d="M5 22h14M5 2h14M17 2v4l-5 4 5 4v4H7v-4l5-4-5-4V2"/>`,
327
+ // ── Medical & Transport ──────────────────────────────────────────────────
328
+ 'ambulance': `<rect x="2" y="7" width="14" height="10" rx="1"/><path d="M16 10l5 1v5h-5"/><circle cx="6" cy="17" r="2"/><circle cx="17" cy="17" r="2"/><path d="M6 10h4M8 8v4"/>`,
329
+ 'car': `<rect x="2" y="10" width="20" height="7" rx="2"/><path d="M6 10l2-4h8l2 4"/><circle cx="6.5" cy="17" r="1.5"/><circle cx="17.5" cy="17" r="1.5"/>`,
330
+ 'car-crash': `<rect x="2" y="10" width="14" height="7" rx="2"/><path d="M5 10l2-4h6l2 4"/><circle cx="5.5" cy="17" r="1.5"/><circle cx="12.5" cy="17" r="1.5"/><line x1="19" y1="4" x2="23" y2="8"/><line x1="23" y1="4" x2="19" y2="8"/>`,
331
+ 'bed': `<path d="M2 4v16"/><path d="M2 8h18a2 2 0 0 1 2 2v4H2"/><path d="M2 17h20"/><circle cx="6" cy="8" r="2"/>`,
332
+ 'xray': `<rect x="3" y="2" width="18" height="20" rx="2"/><line x1="12" y1="6" x2="12" y2="18"/><path d="M9 8c0 0 3 2 3 4s-3 4-3 4"/><path d="M15 8c0 0-3 2-3 4s3 4 3 4"/>`,
333
+ 'lab': `<path d="M9 3h6"/><path d="M9 3v7l-5 9a1 1 0 0 0 .9 1.5h12.2a1 1 0 0 0 .9-1.5L15 10V3"/><circle cx="11" cy="16" r="1"/>`,
334
+ // ── Social ───────────────────────────────────────────────────────────────
335
+ 'youtube': `<path d="M22.54 6.42a2.78 2.78 0 0 0-1.95-1.95C18.88 4 12 4 12 4s-6.88 0-8.59.47a2.78 2.78 0 0 0-1.95 1.95A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.41 19.1C5.12 19.56 12 19.56 12 19.56s6.88 0 8.59-.46a2.78 2.78 0 0 0 1.95-1.95 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.48z"/><polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02"/>`,
336
+ 'facebook': `<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"/>`,
337
+ 'line': `<path d="M20 12c0-4.4-3.58-8-8-8S4 7.6 4 12c0 3.74 2.7 6.9 6.41 7.6.25.05.59.16.68.36.08.18.05.47.02.66l-.11.67c-.03.2-.17.8.64.43 4.8-2.57 7.36-6.23 7.36-9.72z"/><line x1="9" y1="11" x2="9" y2="13"/><line x1="11.25" y1="11" x2="11.25" y2="13"/><line x1="13.5" y1="11" x2="13.5" y2="13"/><line x1="15" y1="11" x2="15" y2="13"/>`,
338
+ 'telegram': `<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>`,
339
+ 'wechat': `<path d="M9 3C5.1 3 2 5.9 2 9.5c0 2 1 3.8 2.6 5l-.6 2 2.4-1.1c.8.3 1.7.5 2.6.5 4.4 0 7-2.9 7-6.4C16 5.9 12.9 3 9 3z"/><path d="M16 9.2c.5-.1 1-.1 1.5-.1C21.1 9.1 24 11.6 24 14.7c0 1.5-.7 2.9-1.9 3.9l.5 1.8-2-1c-.6.2-1.3.3-2 .3-3 0-5.5-2-5.5-4.5 0-.4.1-.8.2-1.2"/>`,
340
+ 'linkedin': `<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"/><rect x="2" y="9" width="4" height="12"/><circle cx="4" cy="4" r="2"/>`,
341
+ };
342
+
343
+ class PkIcon {
344
+ name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
345
+ iconSet = input('pk', ...(ngDevMode ? [{ debugName: "iconSet" }] : /* istanbul ignore next */ []));
346
+ size = input(15, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
347
+ color = input('currentColor', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
348
+ fillColor = input('none', ...(ngDevMode ? [{ debugName: "fillColor" }] : /* istanbul ignore next */ []));
349
+ viewBox = input('0 0 24 24', ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
350
+ strokeWidth = input(2, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : /* istanbul ignore next */ []));
351
+ // Material Symbols specific controls
352
+ variant = input('outlined', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
353
+ fill = input(0, ...(ngDevMode ? [{ debugName: "fill" }] : /* istanbul ignore next */ []));
354
+ weight = input(400, ...(ngDevMode ? [{ debugName: "weight" }] : /* istanbul ignore next */ []));
355
+ grade = input(0, ...(ngDevMode ? [{ debugName: "grade" }] : /* istanbul ignore next */ []));
356
+ opticalSize = input(24, ...(ngDevMode ? [{ debugName: "opticalSize" }] : /* istanbul ignore next */ []));
357
+ sanitizer = inject(DomSanitizer);
358
+ svgHtml = computed(() => {
359
+ const paths = PK_ICONS[this.name()] ?? '';
360
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg"` +
361
+ ` width="${this.size()}" height="${this.size()}"` +
362
+ ` viewBox="${this.viewBox()}"` +
363
+ ` stroke="${this.color()}"` +
364
+ ` fill="${this.fillColor()}"` +
365
+ ` stroke-width="${this.strokeWidth()}"` +
366
+ ` stroke-linecap="round" stroke-linejoin="round">` +
367
+ paths +
368
+ `</svg>`;
369
+ return this.sanitizer.bypassSecurityTrustHtml(svg);
370
+ }, ...(ngDevMode ? [{ debugName: "svgHtml" }] : /* istanbul ignore next */ []));
371
+ materialVariantClass = computed(() => {
372
+ if (this.variant() === 'rounded') {
373
+ return 'material-symbols-rounded';
374
+ }
375
+ if (this.variant() === 'sharp') {
376
+ return 'material-symbols-sharp';
377
+ }
378
+ return 'material-symbols-outlined';
379
+ }, ...(ngDevMode ? [{ debugName: "materialVariantClass" }] : /* istanbul ignore next */ []));
380
+ materialVariationSettings = computed(() => {
381
+ return `'FILL' ${this.fill()}, 'wght' ${this.weight()}, 'GRAD' ${this.grade()}, 'opsz' ${this.opticalSize()}`;
382
+ }, ...(ngDevMode ? [{ debugName: "materialVariationSettings" }] : /* istanbul ignore next */ []));
383
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
384
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkIcon, isStandalone: true, selector: "pk-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, iconSet: { classPropertyName: "iconSet", publicName: "iconSet", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, fillColor: { classPropertyName: "fillColor", publicName: "fillColor", isSignal: true, isRequired: false, transformFunction: null }, viewBox: { classPropertyName: "viewBox", publicName: "viewBox", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null }, weight: { classPropertyName: "weight", publicName: "weight", isSignal: true, isRequired: false, transformFunction: null }, grade: { classPropertyName: "grade", publicName: "grade", isSignal: true, isRequired: false, transformFunction: null }, opticalSize: { classPropertyName: "opticalSize", publicName: "opticalSize", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
385
+ @if (iconSet() === 'material-symbols') {
386
+ <span
387
+ class="pk-material-symbol"
388
+ [class.material-symbols-outlined]="materialVariantClass() === 'material-symbols-outlined'"
389
+ [class.material-symbols-rounded]="materialVariantClass() === 'material-symbols-rounded'"
390
+ [class.material-symbols-sharp]="materialVariantClass() === 'material-symbols-sharp'"
391
+ [style.font-size.px]="size()"
392
+ [style.color]="color()"
393
+ [style.font-variation-settings]="materialVariationSettings()"
394
+ >{{ name() }}</span>
395
+ } @else {
396
+ <span [innerHTML]="svgHtml()"></span>
397
+ }
398
+ `, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:1}:host span{display:inline-flex;align-items:center;justify-content:center}:host span svg{display:block}.pk-material-symbol{line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr}\n"] });
399
+ }
400
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkIcon, decorators: [{
401
+ type: Component,
402
+ args: [{ selector: 'pk-icon', template: `
403
+ @if (iconSet() === 'material-symbols') {
404
+ <span
405
+ class="pk-material-symbol"
406
+ [class.material-symbols-outlined]="materialVariantClass() === 'material-symbols-outlined'"
407
+ [class.material-symbols-rounded]="materialVariantClass() === 'material-symbols-rounded'"
408
+ [class.material-symbols-sharp]="materialVariantClass() === 'material-symbols-sharp'"
409
+ [style.font-size.px]="size()"
410
+ [style.color]="color()"
411
+ [style.font-variation-settings]="materialVariationSettings()"
412
+ >{{ name() }}</span>
413
+ } @else {
414
+ <span [innerHTML]="svgHtml()"></span>
415
+ }
416
+ `, styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:1}:host span{display:inline-flex;align-items:center;justify-content:center}:host span svg{display:block}.pk-material-symbol{line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr}\n"] }]
417
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], iconSet: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconSet", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], fillColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "fillColor", required: false }] }], viewBox: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewBox", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], fill: [{ type: i0.Input, args: [{ isSignal: true, alias: "fill", required: false }] }], weight: [{ type: i0.Input, args: [{ isSignal: true, alias: "weight", required: false }] }], grade: [{ type: i0.Input, args: [{ isSignal: true, alias: "grade", required: false }] }], opticalSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "opticalSize", required: false }] }] } });
418
+
419
+ class PkDgColumnComponent {
420
+ el;
421
+ pkDgField;
422
+ pkDgFilterKey;
423
+ widthPx;
424
+ width;
425
+ headerText = '';
426
+ constructor(el) {
427
+ this.el = el;
428
+ }
429
+ ngAfterViewInit() {
430
+ // Extract text content for header
431
+ const nativeElement = this.el.nativeElement;
432
+ this.headerText = nativeElement.textContent?.trim() || '';
433
+ // @Input('style.width.px') is bypassed by Angular's style binding pipeline —
434
+ // read from the host element's actual inline style as the reliable source
435
+ if (!this.widthPx) {
436
+ const styleWidth = nativeElement.style.width;
437
+ if (styleWidth && styleWidth.endsWith('px')) {
438
+ this.widthPx = parseInt(styleWidth, 10);
439
+ }
440
+ }
441
+ }
442
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgColumnComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
443
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: PkDgColumnComponent, isStandalone: false, selector: "pk-dg-column", inputs: { pkDgField: "pkDgField", pkDgFilterKey: "pkDgFilterKey", widthPx: ["style.width.px", "widthPx"] }, host: { styleAttribute: "display: none" }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true });
444
+ }
445
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgColumnComponent, decorators: [{
446
+ type: Component,
447
+ args: [{
448
+ selector: 'pk-dg-column',
449
+ template: '<ng-content></ng-content>',
450
+ standalone: false,
451
+ host: {
452
+ 'style': 'display: none'
453
+ }
454
+ }]
455
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { pkDgField: [{
456
+ type: Input
457
+ }], pkDgFilterKey: [{
458
+ type: Input
459
+ }], widthPx: [{
460
+ type: Input,
461
+ args: ['style.width.px']
462
+ }] } });
463
+
464
+ class PkDgPageSizeComponent {
465
+ cdr;
466
+ pkPageSizeOptions = [10, 20, 50, 100];
467
+ pagination = null;
468
+ constructor(cdr) {
469
+ this.cdr = cdr;
470
+ }
471
+ setPagination(p) {
472
+ this.pagination = p;
473
+ this.cdr.detectChanges();
474
+ }
475
+ get currentSize() {
476
+ return this.pagination?.pkDgPageSize ?? this.pkPageSizeOptions[0];
477
+ }
478
+ onChange(event) {
479
+ const value = +event.target.value;
480
+ this.pagination?.onPageSizeChange(value);
481
+ }
482
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgPageSizeComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
483
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkDgPageSizeComponent, isStandalone: false, selector: "pk-dg-page-size", inputs: { pkPageSizeOptions: "pkPageSizeOptions" }, ngImport: i0, template: "<span class=\"pk-dg-page-size\">\n <ng-content></ng-content>\n <select (change)=\"onChange($event)\">\n @for (opt of pkPageSizeOptions; track opt) {\n <option [value]=\"opt\" [selected]=\"opt === currentSize\">{{ opt }}</option>\n }\n </select>\n</span>\n", styles: [".pk-dg-page-size{display:inline-flex;align-items:center;gap:6px;margin-right:16px;font-size:13px;color:#555}.pk-dg-page-size select{padding:2px 6px;border:1px solid #ccc;border-radius:3px;font-size:13px;background:#fff;cursor:pointer}.pk-dg-page-size select:focus{outline:none;border-color:#0072a3}\n"], dependencies: [{ kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }] });
484
+ }
485
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgPageSizeComponent, decorators: [{
486
+ type: Component,
487
+ args: [{ selector: 'pk-dg-page-size', standalone: false, template: "<span class=\"pk-dg-page-size\">\n <ng-content></ng-content>\n <select (change)=\"onChange($event)\">\n @for (opt of pkPageSizeOptions; track opt) {\n <option [value]=\"opt\" [selected]=\"opt === currentSize\">{{ opt }}</option>\n }\n </select>\n</span>\n", styles: [".pk-dg-page-size{display:inline-flex;align-items:center;gap:6px;margin-right:16px;font-size:13px;color:#555}.pk-dg-page-size select{padding:2px 6px;border:1px solid #ccc;border-radius:3px;font-size:13px;background:#fff;cursor:pointer}.pk-dg-page-size select:focus{outline:none;border-color:#0072a3}\n"] }]
488
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { pkPageSizeOptions: [{
489
+ type: Input
490
+ }] } });
491
+
492
+ class PkDgPaginationComponent {
493
+ pageSize;
494
+ pkDgPageSize = 10;
495
+ totalItems = 0;
496
+ pkDgPageChange = new EventEmitter();
497
+ pkDgPageSizeChange = new EventEmitter();
498
+ currentPage = 1;
499
+ firstItem = 0;
500
+ lastItem = 0;
501
+ ngOnInit() {
502
+ this.updatePagination();
503
+ }
504
+ ngAfterContentInit() {
505
+ if (this.pageSize) {
506
+ this.pageSize.setPagination(this);
507
+ }
508
+ }
509
+ ngOnChanges() {
510
+ this.updatePagination();
511
+ }
512
+ updatePagination() {
513
+ this.firstItem = (this.currentPage - 1) * this.pkDgPageSize;
514
+ this.lastItem = Math.min(this.firstItem + this.pkDgPageSize, this.totalItems);
515
+ }
516
+ get totalPages() {
517
+ return Math.ceil(this.totalItems / this.pkDgPageSize);
518
+ }
519
+ goToPage(page) {
520
+ if (page >= 1 && page <= this.totalPages) {
521
+ this.currentPage = page;
522
+ this.updatePagination();
523
+ this.pkDgPageChange.emit(page);
524
+ }
525
+ }
526
+ onPageSizeChange(size) {
527
+ this.pkDgPageSize = size;
528
+ this.currentPage = 1;
529
+ this.updatePagination();
530
+ this.pkDgPageSizeChange.emit(size);
531
+ }
532
+ get pages() {
533
+ const pages = [];
534
+ const maxVisible = 5;
535
+ let start = Math.max(1, this.currentPage - Math.floor(maxVisible / 2));
536
+ let end = Math.min(this.totalPages, start + maxVisible - 1);
537
+ if (end - start + 1 < maxVisible) {
538
+ start = Math.max(1, end - maxVisible + 1);
539
+ }
540
+ for (let i = start; i <= end; i++) {
541
+ pages.push(i);
542
+ }
543
+ return pages;
544
+ }
545
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgPaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
546
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkDgPaginationComponent, isStandalone: false, selector: "pk-dg-pagination", inputs: { pkDgPageSize: "pkDgPageSize", totalItems: "totalItems" }, outputs: { pkDgPageChange: "pkDgPageChange", pkDgPageSizeChange: "pkDgPageSizeChange" }, queries: [{ propertyName: "pageSize", first: true, predicate: PkDgPageSizeComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"pk-dg-pagination\">\n <div class=\"pagination-info\">\n <ng-content></ng-content>\n </div>\n \n <div class=\"pagination-controls\">\n <button \n class=\"btn btn-sm btn-link\"\n [disabled]=\"currentPage === 1\"\n (click)=\"goToPage(currentPage - 1)\">\n \u2039\n </button>\n \n @for (page of pages; track page) {\n <button \n class=\"btn btn-sm\"\n [class.btn-primary]=\"page === currentPage\"\n [class.btn-link]=\"page !== currentPage\"\n (click)=\"goToPage(page)\">\n {{ page }}\n </button>\n }\n \n <button \n class=\"btn btn-sm btn-link\"\n [disabled]=\"currentPage === totalPages\"\n (click)=\"goToPage(currentPage + 1)\">\n \u203A\n </button>\n </div>\n</div>\n", styles: [".pk-dg-pagination{display:flex;align-items:center;justify-content:space-between;padding:12px;border-top:1px solid #e0e0e0;background:#fafafa}.pk-dg-pagination .pagination-info{color:#565656;font-size:14px}.pk-dg-pagination .pagination-controls{display:flex;gap:4px}.pk-dg-pagination .pagination-controls .btn{min-width:32px;padding:4px 8px}.pk-dg-pagination .pagination-controls .btn:disabled{opacity:.5;cursor:not-allowed}\n"] });
547
+ }
548
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgPaginationComponent, decorators: [{
549
+ type: Component,
550
+ args: [{ selector: 'pk-dg-pagination', standalone: false, template: "<div class=\"pk-dg-pagination\">\n <div class=\"pagination-info\">\n <ng-content></ng-content>\n </div>\n \n <div class=\"pagination-controls\">\n <button \n class=\"btn btn-sm btn-link\"\n [disabled]=\"currentPage === 1\"\n (click)=\"goToPage(currentPage - 1)\">\n \u2039\n </button>\n \n @for (page of pages; track page) {\n <button \n class=\"btn btn-sm\"\n [class.btn-primary]=\"page === currentPage\"\n [class.btn-link]=\"page !== currentPage\"\n (click)=\"goToPage(page)\">\n {{ page }}\n </button>\n }\n \n <button \n class=\"btn btn-sm btn-link\"\n [disabled]=\"currentPage === totalPages\"\n (click)=\"goToPage(currentPage + 1)\">\n \u203A\n </button>\n </div>\n</div>\n", styles: [".pk-dg-pagination{display:flex;align-items:center;justify-content:space-between;padding:12px;border-top:1px solid #e0e0e0;background:#fafafa}.pk-dg-pagination .pagination-info{color:#565656;font-size:14px}.pk-dg-pagination .pagination-controls{display:flex;gap:4px}.pk-dg-pagination .pagination-controls .btn{min-width:32px;padding:4px 8px}.pk-dg-pagination .pagination-controls .btn:disabled{opacity:.5;cursor:not-allowed}\n"] }]
551
+ }], propDecorators: { pageSize: [{
552
+ type: ContentChild,
553
+ args: [PkDgPageSizeComponent]
554
+ }], pkDgPageSize: [{
555
+ type: Input
556
+ }], totalItems: [{
557
+ type: Input
558
+ }], pkDgPageChange: [{
559
+ type: Output
560
+ }], pkDgPageSizeChange: [{
561
+ type: Output
562
+ }] } });
563
+
564
+ class PkDgRowComponent {
565
+ pkDgItem;
566
+ detailTemplate;
567
+ isExpanded = false;
568
+ hasDetail = false;
569
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgRowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
570
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkDgRowComponent, isStandalone: false, selector: "pk-dg-row", inputs: { pkDgItem: "pkDgItem" }, host: { styleAttribute: "display: contents" }, queries: [{ propertyName: "detailTemplate", first: true, predicate: ["detail"], descendants: true }], ngImport: i0, template: "<tr class=\"pk-dg-row\">\n @if (hasDetail) {\n <td class=\"pk-dg-expand-btn-cell\">\n <button class=\"pk-dg-expand-btn\" [class.expanded]=\"isExpanded\" (click)=\"isExpanded=!isExpanded; $event.stopPropagation()\" title=\"\u0E02\u0E22\u0E32\u0E22/\u0E22\u0E48\u0E2D\">\n <pk-icon\n iconSet=\"material-symbols\"\n [name]=\"isExpanded ? 'keyboard_arrow_down' : 'keyboard_arrow_right'\"\n [size]=\"16\"\n color=\"currentColor\"\n ></pk-icon>\n </button>\n </td>\n } @else {\n <td class=\"pk-dg-expand-btn-cell pk-dg-no-detail\"></td>\n }\n <ng-content></ng-content>\n</tr>\n@if (isExpanded) {\n <tr class=\"pk-dg-row-detail\">\n <td [attr.colspan]=\"999\">\n <ng-content select=\"pk-dg-row-detail\"></ng-content>\n </td>\n </tr>\n}\n", styles: [":host{display:contents}.pk-dg-expand-btn-cell{width:28px;min-width:28px;padding:0 2px;text-align:center;border:1px solid #e8e8e8;vertical-align:middle}.pk-dg-expand-btn-cell.pk-dg-no-detail{width:0;min-width:0;max-width:0;padding:0;border:none;overflow:hidden}.pk-dg-expand-btn{background:none;border:none;cursor:pointer;padding:2px;color:#aaa;display:flex;align-items:center;justify-content:center;border-radius:3px;transition:color .15s,background .15s;width:22px;height:22px;margin:auto}.pk-dg-expand-btn:hover{background:#e8f4ff;color:#0072a3}.pk-dg-expand-btn.expanded{color:#0072a3}.pk-dg-chevron{width:14px;height:14px;transition:transform .2s ease;transform:rotate(-90deg)}.pk-dg-expand-btn.expanded .pk-dg-chevron{transform:rotate(0)}\n"], dependencies: [{ kind: "component", type: PkIcon, selector: "pk-icon", inputs: ["name", "iconSet", "size", "color", "fillColor", "viewBox", "strokeWidth", "variant", "fill", "weight", "grade", "opticalSize"] }] });
571
+ }
572
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgRowComponent, decorators: [{
573
+ type: Component,
574
+ args: [{ selector: 'pk-dg-row', standalone: false, host: {
575
+ 'style': 'display: contents'
576
+ }, template: "<tr class=\"pk-dg-row\">\n @if (hasDetail) {\n <td class=\"pk-dg-expand-btn-cell\">\n <button class=\"pk-dg-expand-btn\" [class.expanded]=\"isExpanded\" (click)=\"isExpanded=!isExpanded; $event.stopPropagation()\" title=\"\u0E02\u0E22\u0E32\u0E22/\u0E22\u0E48\u0E2D\">\n <pk-icon\n iconSet=\"material-symbols\"\n [name]=\"isExpanded ? 'keyboard_arrow_down' : 'keyboard_arrow_right'\"\n [size]=\"16\"\n color=\"currentColor\"\n ></pk-icon>\n </button>\n </td>\n } @else {\n <td class=\"pk-dg-expand-btn-cell pk-dg-no-detail\"></td>\n }\n <ng-content></ng-content>\n</tr>\n@if (isExpanded) {\n <tr class=\"pk-dg-row-detail\">\n <td [attr.colspan]=\"999\">\n <ng-content select=\"pk-dg-row-detail\"></ng-content>\n </td>\n </tr>\n}\n", styles: [":host{display:contents}.pk-dg-expand-btn-cell{width:28px;min-width:28px;padding:0 2px;text-align:center;border:1px solid #e8e8e8;vertical-align:middle}.pk-dg-expand-btn-cell.pk-dg-no-detail{width:0;min-width:0;max-width:0;padding:0;border:none;overflow:hidden}.pk-dg-expand-btn{background:none;border:none;cursor:pointer;padding:2px;color:#aaa;display:flex;align-items:center;justify-content:center;border-radius:3px;transition:color .15s,background .15s;width:22px;height:22px;margin:auto}.pk-dg-expand-btn:hover{background:#e8f4ff;color:#0072a3}.pk-dg-expand-btn.expanded{color:#0072a3}.pk-dg-chevron{width:14px;height:14px;transition:transform .2s ease;transform:rotate(-90deg)}.pk-dg-expand-btn.expanded .pk-dg-chevron{transform:rotate(0)}\n"] }]
577
+ }], propDecorators: { pkDgItem: [{
578
+ type: Input
579
+ }], detailTemplate: [{
580
+ type: ContentChild,
581
+ args: ['detail']
582
+ }] } });
583
+
584
+ class PkDgRowDetailComponent {
585
+ row;
586
+ constructor(row) {
587
+ this.row = row;
588
+ }
589
+ ngOnInit() {
590
+ if (this.row) {
591
+ this.row.hasDetail = true;
592
+ }
593
+ }
594
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgRowDetailComponent, deps: [{ token: PkDgRowComponent, host: true, optional: true }], target: i0.ɵɵFactoryTarget.Component });
595
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: PkDgRowDetailComponent, isStandalone: false, selector: "pk-dg-row-detail", host: { styleAttribute: "display: contents" }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true });
596
+ }
597
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgRowDetailComponent, decorators: [{
598
+ type: Component,
599
+ args: [{
600
+ selector: 'pk-dg-row-detail',
601
+ template: '<ng-content></ng-content>',
602
+ standalone: false,
603
+ host: {
604
+ 'style': 'display: contents'
605
+ }
606
+ }]
607
+ }], ctorParameters: () => [{ type: PkDgRowComponent, decorators: [{
608
+ type: Host
609
+ }, {
610
+ type: Optional
611
+ }] }] });
612
+
613
+ class PkDgItemsDirective {
614
+ templateRef;
615
+ viewContainer;
616
+ datagrid;
617
+ _items = [];
618
+ _lastVersion = -1;
619
+ constructor(templateRef, viewContainer, datagrid) {
620
+ this.templateRef = templateRef;
621
+ this.viewContainer = viewContainer;
622
+ this.datagrid = datagrid;
623
+ }
624
+ ngDoCheck() {
625
+ if (this.datagrid) {
626
+ // Use version counter to detect real changes only
627
+ const version = this.datagrid.displayedItemsVersion;
628
+ if (version !== this._lastVersion) {
629
+ this._lastVersion = version;
630
+ this.renderItems();
631
+ }
632
+ }
633
+ }
634
+ set pkDgItemsOf(items) {
635
+ this._items = items || [];
636
+ // Push items to parent datagrid so it can handle pagination/sorting
637
+ if (this.datagrid) {
638
+ this.datagrid.items = this._items;
639
+ if (this.datagrid.pagination) {
640
+ this.datagrid.pagination.totalItems = this._items.length;
641
+ this.datagrid.pagination.updatePagination();
642
+ }
643
+ this.datagrid.updateDisplayedItems();
644
+ }
645
+ }
646
+ renderItems() {
647
+ this.viewContainer.clear();
648
+ const items = this.datagrid ? this.datagrid.displayedItems : this._items;
649
+ if (items && items.length > 0) {
650
+ items.forEach((item, index) => {
651
+ this.viewContainer.createEmbeddedView(this.templateRef, {
652
+ $implicit: item,
653
+ index: index,
654
+ count: items.length
655
+ });
656
+ });
657
+ }
658
+ }
659
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgItemsDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: forwardRef(() => PkDatagridComponent), optional: true }], target: i0.ɵɵFactoryTarget.Directive });
660
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: PkDgItemsDirective, isStandalone: false, selector: "[pkDgItems]", inputs: { pkDgItemsOf: "pkDgItemsOf" }, ngImport: i0 });
661
+ }
662
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgItemsDirective, decorators: [{
663
+ type: Directive,
664
+ args: [{
665
+ selector: '[pkDgItems]',
666
+ standalone: false
667
+ }]
668
+ }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: PkDatagridComponent, decorators: [{
669
+ type: Optional
670
+ }, {
671
+ type: Inject,
672
+ args: [forwardRef(() => PkDatagridComponent)]
673
+ }] }], propDecorators: { pkDgItemsOf: [{
674
+ type: Input
675
+ }] } });
676
+ class PkIfExpandedDirective {
677
+ templateRef;
678
+ viewContainer;
679
+ row;
680
+ constructor(templateRef, viewContainer, row) {
681
+ this.templateRef = templateRef;
682
+ this.viewContainer = viewContainer;
683
+ this.row = row;
684
+ // Mark row as having detail as soon as this directive is instantiated
685
+ if (this.row) {
686
+ this.row.hasDetail = true;
687
+ }
688
+ }
689
+ ngAfterViewInit() {
690
+ this.updateView();
691
+ }
692
+ ngDoCheck() {
693
+ this.updateView();
694
+ }
695
+ updateView() {
696
+ if (this.row && this.row.isExpanded) {
697
+ if (this.viewContainer.length === 0) {
698
+ this.viewContainer.createEmbeddedView(this.templateRef);
699
+ }
700
+ }
701
+ else {
702
+ this.viewContainer.clear();
703
+ }
704
+ }
705
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkIfExpandedDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: PkDgRowComponent, host: true, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
706
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: PkIfExpandedDirective, isStandalone: false, selector: "[pkIfExpanded]", ngImport: i0 });
707
+ }
708
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkIfExpandedDirective, decorators: [{
709
+ type: Directive,
710
+ args: [{
711
+ selector: '[pkIfExpanded]',
712
+ standalone: false
713
+ }]
714
+ }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: PkDgRowComponent, decorators: [{
715
+ type: Host
716
+ }, {
717
+ type: Optional
718
+ }] }] });
719
+ class NowrapDirective {
720
+ nowrap = true;
721
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NowrapDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
722
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: NowrapDirective, isStandalone: false, selector: "[nowrap]", host: { properties: { "style.white-space": "nowrap ? \"nowrap\" : \"normal\"" } }, ngImport: i0 });
723
+ }
724
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NowrapDirective, decorators: [{
725
+ type: Directive,
726
+ args: [{
727
+ selector: '[nowrap]',
728
+ standalone: false,
729
+ host: {
730
+ '[style.white-space]': 'nowrap ? "nowrap" : "normal"'
731
+ }
732
+ }]
733
+ }] });
734
+
735
+ class PkDatagridComponent {
736
+ cdr;
737
+ columns;
738
+ pagination;
739
+ rowDetails;
740
+ expandDirectives;
741
+ get hasExpandCol() {
742
+ if (this.expandDirectives && this.expandDirectives.length > 0)
743
+ return true;
744
+ return this.rowDetails ? this.rowDetails.length > 0 : false;
745
+ }
746
+ pkDgLoading = false;
747
+ items = [];
748
+ filterValues = {};
749
+ pkDgRefresh = new EventEmitter();
750
+ filterChange = new EventEmitter();
751
+ displayedItems = [];
752
+ displayedItemsVersion = 0;
753
+ expandedRows = new Set();
754
+ sortColumn = null;
755
+ sortDirection = null;
756
+ activeFilterCol = null;
757
+ activeFilterHeader = '';
758
+ filterPopupPos = { top: 0, left: 0 };
759
+ // Column resize
760
+ columnWidths = [];
761
+ _resizingIdx = -1;
762
+ _resizeStartX = 0;
763
+ _resizeStartWidth = 0;
764
+ _resizeMoveRef = (e) => this._onResizeMove(e);
765
+ _resizeEndRef = () => this._onResizeEnd();
766
+ get totalWidth() {
767
+ const sum = this.columnWidths.reduce((acc, w) => acc + w, 0);
768
+ return sum + (this.hasExpandCol ? 32 : 0);
769
+ }
770
+ _initColumnWidths() {
771
+ const cols = this.columns ? this.columns.toArray() : [];
772
+ this.columnWidths = cols.map(c => c.widthPx ?? 120);
773
+ }
774
+ onDocumentClick() {
775
+ this.activeFilterCol = null;
776
+ }
777
+ constructor(cdr) {
778
+ this.cdr = cdr;
779
+ }
780
+ ngAfterContentInit() {
781
+ // Listen to pagination changes first
782
+ if (this.pagination) {
783
+ this.pagination.pkDgPageChange.subscribe(() => this.updateDisplayedItems());
784
+ this.pagination.pkDgPageSizeChange.subscribe(() => this.updateDisplayedItems());
785
+ }
786
+ // Update displayed items after pagination is set up
787
+ this.updateDisplayedItems();
788
+ }
789
+ ngOnChanges(changes) {
790
+ if (changes['items']) {
791
+ this.updateDisplayedItems();
792
+ }
793
+ if (changes['filterValues']) {
794
+ this.updateDisplayedItems();
795
+ }
796
+ }
797
+ ngAfterViewInit() {
798
+ // Force change detection after columns are fully initialized
799
+ // Also re-update displayed items to ensure pagination is initialized
800
+ setTimeout(() => {
801
+ if (this.pagination) {
802
+ this.pagination.totalItems = this.items.length;
803
+ this.pagination.updatePagination();
804
+ }
805
+ this._initColumnWidths();
806
+ this.updateDisplayedItems();
807
+ }, 0);
808
+ }
809
+ updateDisplayedItems() {
810
+ // If no items, set empty array
811
+ if (!this.items || this.items.length === 0) {
812
+ this.displayedItems = [];
813
+ return;
814
+ }
815
+ let result = [...this.items];
816
+ // Apply column filters
817
+ const filters = this.filterValues || {};
818
+ const activeFilterKeys = Object.keys(filters).filter((key) => {
819
+ const value = filters[key];
820
+ return value !== undefined && value !== null && `${value}`.trim() !== '';
821
+ });
822
+ if (activeFilterKeys.length > 0) {
823
+ result = result.filter((row) => {
824
+ return activeFilterKeys.every((key) => {
825
+ const raw = row?.[key];
826
+ const source = raw === undefined || raw === null ? '' : `${raw}`;
827
+ const target = `${filters[key]}`.trim();
828
+ return source.toLowerCase().includes(target.toLowerCase());
829
+ });
830
+ });
831
+ }
832
+ // Keep pagination counts in sync with filtered result
833
+ if (this.pagination) {
834
+ this.pagination.totalItems = result.length;
835
+ if (this.pagination.currentPage > this.pagination.totalPages && this.pagination.totalPages > 0) {
836
+ this.pagination.currentPage = this.pagination.totalPages;
837
+ }
838
+ if (this.pagination.currentPage < 1) {
839
+ this.pagination.currentPage = 1;
840
+ }
841
+ this.pagination.updatePagination();
842
+ }
843
+ // Apply sorting
844
+ if (this.sortColumn && this.sortDirection) {
845
+ result.sort((a, b) => {
846
+ const aVal = a[this.sortColumn];
847
+ const bVal = b[this.sortColumn];
848
+ if (aVal == null)
849
+ return 1;
850
+ if (bVal == null)
851
+ return -1;
852
+ if (typeof aVal === 'string') {
853
+ const comparison = aVal.localeCompare(bVal);
854
+ return this.sortDirection === 'asc' ? comparison : -comparison;
855
+ }
856
+ if (aVal < bVal)
857
+ return this.sortDirection === 'asc' ? -1 : 1;
858
+ if (aVal > bVal)
859
+ return this.sortDirection === 'asc' ? 1 : -1;
860
+ return 0;
861
+ });
862
+ }
863
+ // Apply pagination
864
+ if (this.pagination) {
865
+ const start = this.pagination.firstItem;
866
+ const end = this.pagination.lastItem;
867
+ result = result.slice(start, end);
868
+ }
869
+ this.displayedItems = result;
870
+ this.displayedItemsVersion++;
871
+ }
872
+ onSort(field) {
873
+ if (this.sortColumn === field) {
874
+ if (this.sortDirection === 'asc') {
875
+ this.sortDirection = 'desc';
876
+ }
877
+ else if (this.sortDirection === 'desc') {
878
+ this.sortColumn = null;
879
+ this.sortDirection = null;
880
+ }
881
+ else {
882
+ this.sortDirection = 'asc';
883
+ }
884
+ }
885
+ else {
886
+ this.sortColumn = field;
887
+ this.sortDirection = 'asc';
888
+ }
889
+ this.updateDisplayedItems();
890
+ }
891
+ getSortIcon(field) {
892
+ if (this.sortColumn !== field)
893
+ return '';
894
+ return this.sortDirection === 'asc' ? '▲' : '▼';
895
+ }
896
+ toggleRowExpansion(row) {
897
+ if (this.expandedRows.has(row)) {
898
+ this.expandedRows.delete(row);
899
+ }
900
+ else {
901
+ this.expandedRows.add(row);
902
+ }
903
+ }
904
+ isRowExpanded(row) {
905
+ return this.expandedRows.has(row);
906
+ }
907
+ openFilter(key, event, headerText = '') {
908
+ event.stopPropagation();
909
+ if (this.activeFilterCol === key) {
910
+ this.activeFilterCol = null;
911
+ return;
912
+ }
913
+ const rect = event.currentTarget.getBoundingClientRect();
914
+ const popupWidth = 180;
915
+ const margin = 8;
916
+ let left = rect.left;
917
+ if (left + popupWidth + margin > window.innerWidth) {
918
+ left = rect.right - popupWidth;
919
+ }
920
+ if (left < margin) {
921
+ left = margin;
922
+ }
923
+ this.filterPopupPos = { top: rect.bottom + 4, left };
924
+ this.activeFilterHeader = headerText;
925
+ this.activeFilterCol = key;
926
+ }
927
+ onFilterInput(key, event) {
928
+ const value = event.target.value;
929
+ this.filterValues = { ...this.filterValues, [key]: value };
930
+ if (this.pagination) {
931
+ this.pagination.currentPage = 1;
932
+ this.pagination.updatePagination();
933
+ }
934
+ this.updateDisplayedItems();
935
+ this.filterChange.emit({ key, value });
936
+ }
937
+ // ── Column resize ───────────────────────────────────────────────
938
+ startResize(colIdx, event) {
939
+ event.preventDefault();
940
+ event.stopPropagation();
941
+ this._resizingIdx = colIdx;
942
+ this._resizeStartX = event.clientX;
943
+ this._resizeStartWidth = this.columnWidths[colIdx];
944
+ document.addEventListener('mousemove', this._resizeMoveRef);
945
+ document.addEventListener('mouseup', this._resizeEndRef);
946
+ document.body.style.userSelect = 'none';
947
+ document.body.style.cursor = 'col-resize';
948
+ }
949
+ _onResizeMove(event) {
950
+ if (this._resizingIdx < 0)
951
+ return;
952
+ const delta = event.clientX - this._resizeStartX;
953
+ this.columnWidths[this._resizingIdx] = Math.max(40, this._resizeStartWidth + delta);
954
+ }
955
+ _onResizeEnd() {
956
+ this._resizingIdx = -1;
957
+ document.removeEventListener('mousemove', this._resizeMoveRef);
958
+ document.removeEventListener('mouseup', this._resizeEndRef);
959
+ document.body.style.userSelect = '';
960
+ document.body.style.cursor = '';
961
+ }
962
+ ngOnDestroy() {
963
+ this._onResizeEnd();
964
+ }
965
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDatagridComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
966
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkDatagridComponent, isStandalone: false, selector: "pk-datagrid", inputs: { pkDgLoading: "pkDgLoading", items: "items", filterValues: "filterValues" }, outputs: { pkDgRefresh: "pkDgRefresh", filterChange: "filterChange" }, host: { listeners: { "document:click": "onDocumentClick()" } }, queries: [{ propertyName: "pagination", first: true, predicate: PkDgPaginationComponent, descendants: true }, { propertyName: "columns", predicate: PkDgColumnComponent }, { propertyName: "rowDetails", predicate: PkDgRowDetailComponent, descendants: true }, { propertyName: "expandDirectives", predicate: PkIfExpandedDirective, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"pk-datagrid-container\">\n @if (pkDgLoading) {\n <div class=\"pk-datagrid-spinner\">\n <div class=\"spinner\"></div>\n <span>Loading...</span>\n </div>\n }\n \n <table class=\"pk-datagrid\" style=\"table-layout: fixed\" [style.min-width.px]=\"totalWidth\">\n <thead>\n <tr>\n @if (hasExpandCol) {\n <th class=\"pk-dg-expand-th\"></th>\n } @else {\n <th class=\"pk-dg-expand-th pk-dg-no-detail\"></th>\n }\n @for (column of columns; track column.pkDgField; let i = $index) {\n <th [style.width.px]=\"columnWidths[i]\">\n <div class=\"th-content\">\n <span>{{ column.headerText }}</span>\n <div class=\"th-actions\">\n @if (column.pkDgField) {\n <button class=\"sort-icon-btn\"\n [class.active]=\"sortColumn === column.pkDgField\"\n (click)=\"onSort(column.pkDgField)\"\n title=\"\u0E40\u0E23\u0E35\u0E22\u0E07\u0E25\u0E33\u0E14\u0E31\u0E1A\">\n <pk-icon\n iconSet=\"material-symbols\"\n [name]=\"sortColumn === column.pkDgField ? (sortDirection === 'asc' ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : 'unfold_more'\"\n [size]=\"12\" color=\"currentColor\">\n </pk-icon>\n </button>\n }\n @if (column.pkDgFilterKey) {\n <button class=\"filter-icon-btn\"\n [class.active]=\"filterValues[column.pkDgFilterKey]\"\n (click)=\"openFilter(column.pkDgFilterKey, $event, column.headerText)\"\n title=\"\u0E01\u0E23\u0E2D\u0E07\">\n <pk-icon name=\"search\" [size]=\"12\" color=\"currentColor\"></pk-icon>\n </button>\n }\n </div>\n </div>\n @if (column.pkDgFilterKey && activeFilterCol === column.pkDgFilterKey) {\n <div class=\"filter-popup\"\n [style.top.px]=\"filterPopupPos.top\"\n [style.left.px]=\"filterPopupPos.left\"\n (click)=\"$event.stopPropagation()\">\n <input type=\"text\"\n [value]=\"filterValues[column.pkDgFilterKey] || ''\"\n (input)=\"onFilterInput(column.pkDgFilterKey, $event)\"\n [placeholder]=\"'\u0E04\u0E49\u0E19\u0E2B\u0E32 ' + activeFilterHeader\"\n autofocus>\n </div>\n }\n <div class=\"resize-handle\" (mousedown)=\"startResize(i, $event)\"></div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n <ng-content select=\"pk-dg-row\"></ng-content>\n </tbody>\n </table>\n \n <ng-content select=\"pk-dg-footer\"></ng-content>\n</div>\n", styles: [".pk-datagrid-container{position:relative;width:100%;overflow:auto}.pk-datagrid-spinner{position:absolute;inset:0;background:#fffc;display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:1000}.pk-datagrid-spinner .spinner{border:4px solid #f3f3f3;border-top:4px solid #0072a3;border-radius:50%;width:40px;height:40px;animation:spin 1s linear infinite}.pk-datagrid-spinner span{margin-top:10px;color:#0072a3}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pk-datagrid{width:100%;border-collapse:collapse;background:#fff;border:1px solid #cccccc}.pk-datagrid thead{background:#f8f8f8}.pk-datagrid thead th{padding:10px 8px;text-align:left;font-weight:600;color:#333;border:1px solid #cccccc;white-space:nowrap;position:sticky;top:0;background:#f8f8f8;z-index:10;overflow:hidden;box-sizing:border-box}.pk-datagrid thead th .th-content{display:flex;align-items:center;justify-content:space-between;gap:4px}.pk-datagrid thead th .th-content .th-actions{display:flex;align-items:center;gap:2px;flex-shrink:0}.pk-datagrid thead th .th-content .sort-icon-btn{background:none;border:none;padding:1px 2px;cursor:pointer;color:#ccc;border-radius:2px;display:flex;align-items:center;line-height:1}.pk-datagrid thead th .th-content .sort-icon-btn:hover{color:#0072a3;background:#0072a31a}.pk-datagrid thead th .th-content .sort-icon-btn.active{color:#0072a3}.pk-datagrid thead th .th-content .filter-icon-btn{background:none;border:none;padding:1px 2px;cursor:pointer;color:#bbb;border-radius:2px;display:flex;align-items:center;line-height:1}.pk-datagrid thead th .th-content .filter-icon-btn:hover{color:#0072a3;background:#0072a31a}.pk-datagrid thead th .th-content .filter-icon-btn.active{color:#0072a3}.pk-datagrid thead th .filter-popup{position:fixed;background:#fff;border:1px solid #0072a3;border-radius:5px;padding:8px;box-shadow:0 4px 14px #00000026;z-index:9999;min-width:180px}.pk-datagrid thead th .filter-popup input{width:100%;box-sizing:border-box;padding:5px 8px;border:1px solid #b8cfe0;border-radius:3px;outline:none}.pk-datagrid thead th .filter-popup input:focus{border-color:#0072a3;box-shadow:0 0 0 2px #0072a32e}.pk-datagrid thead th .resize-handle{position:absolute;top:0;right:0;width:5px;height:100%;cursor:col-resize;z-index:20}.pk-datagrid thead th .resize-handle:after{content:\"\";position:absolute;top:15%;right:2px;width:2px;height:70%;background:transparent;border-radius:1px;transition:background .15s}.pk-datagrid thead th .resize-handle:hover:after{background:#0072a3}.pk-datagrid tbody tr.pk-dg-row{border:1px solid #cccccc;transition:background-color .15s}.pk-datagrid tbody tr.pk-dg-row:hover{background:#f0f9ff;cursor:pointer}.pk-datagrid tbody tr.pk-dg-row-detail,.pk-datagrid tbody tr.pk-dg-row-detail:hover{background:#fafafa}.pk-datagrid tbody tr.pk-dg-row-detail td{padding:15px;border:1px solid #cccccc}.pk-datagrid .pk-dg-expand-th{width:32px;min-width:32px;padding:0}.pk-datagrid .pk-dg-expand-th.pk-dg-no-detail{width:0;min-width:0;max-width:0;padding:0;border:none;overflow:hidden}\n"], dependencies: [{ kind: "component", type: PkIcon, selector: "pk-icon", inputs: ["name", "iconSet", "size", "color", "fillColor", "viewBox", "strokeWidth", "variant", "fill", "weight", "grade", "opticalSize"] }] });
967
+ }
968
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDatagridComponent, decorators: [{
969
+ type: Component,
970
+ args: [{ selector: 'pk-datagrid', standalone: false, template: "<div class=\"pk-datagrid-container\">\n @if (pkDgLoading) {\n <div class=\"pk-datagrid-spinner\">\n <div class=\"spinner\"></div>\n <span>Loading...</span>\n </div>\n }\n \n <table class=\"pk-datagrid\" style=\"table-layout: fixed\" [style.min-width.px]=\"totalWidth\">\n <thead>\n <tr>\n @if (hasExpandCol) {\n <th class=\"pk-dg-expand-th\"></th>\n } @else {\n <th class=\"pk-dg-expand-th pk-dg-no-detail\"></th>\n }\n @for (column of columns; track column.pkDgField; let i = $index) {\n <th [style.width.px]=\"columnWidths[i]\">\n <div class=\"th-content\">\n <span>{{ column.headerText }}</span>\n <div class=\"th-actions\">\n @if (column.pkDgField) {\n <button class=\"sort-icon-btn\"\n [class.active]=\"sortColumn === column.pkDgField\"\n (click)=\"onSort(column.pkDgField)\"\n title=\"\u0E40\u0E23\u0E35\u0E22\u0E07\u0E25\u0E33\u0E14\u0E31\u0E1A\">\n <pk-icon\n iconSet=\"material-symbols\"\n [name]=\"sortColumn === column.pkDgField ? (sortDirection === 'asc' ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : 'unfold_more'\"\n [size]=\"12\" color=\"currentColor\">\n </pk-icon>\n </button>\n }\n @if (column.pkDgFilterKey) {\n <button class=\"filter-icon-btn\"\n [class.active]=\"filterValues[column.pkDgFilterKey]\"\n (click)=\"openFilter(column.pkDgFilterKey, $event, column.headerText)\"\n title=\"\u0E01\u0E23\u0E2D\u0E07\">\n <pk-icon name=\"search\" [size]=\"12\" color=\"currentColor\"></pk-icon>\n </button>\n }\n </div>\n </div>\n @if (column.pkDgFilterKey && activeFilterCol === column.pkDgFilterKey) {\n <div class=\"filter-popup\"\n [style.top.px]=\"filterPopupPos.top\"\n [style.left.px]=\"filterPopupPos.left\"\n (click)=\"$event.stopPropagation()\">\n <input type=\"text\"\n [value]=\"filterValues[column.pkDgFilterKey] || ''\"\n (input)=\"onFilterInput(column.pkDgFilterKey, $event)\"\n [placeholder]=\"'\u0E04\u0E49\u0E19\u0E2B\u0E32 ' + activeFilterHeader\"\n autofocus>\n </div>\n }\n <div class=\"resize-handle\" (mousedown)=\"startResize(i, $event)\"></div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n <ng-content select=\"pk-dg-row\"></ng-content>\n </tbody>\n </table>\n \n <ng-content select=\"pk-dg-footer\"></ng-content>\n</div>\n", styles: [".pk-datagrid-container{position:relative;width:100%;overflow:auto}.pk-datagrid-spinner{position:absolute;inset:0;background:#fffc;display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:1000}.pk-datagrid-spinner .spinner{border:4px solid #f3f3f3;border-top:4px solid #0072a3;border-radius:50%;width:40px;height:40px;animation:spin 1s linear infinite}.pk-datagrid-spinner span{margin-top:10px;color:#0072a3}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pk-datagrid{width:100%;border-collapse:collapse;background:#fff;border:1px solid #cccccc}.pk-datagrid thead{background:#f8f8f8}.pk-datagrid thead th{padding:10px 8px;text-align:left;font-weight:600;color:#333;border:1px solid #cccccc;white-space:nowrap;position:sticky;top:0;background:#f8f8f8;z-index:10;overflow:hidden;box-sizing:border-box}.pk-datagrid thead th .th-content{display:flex;align-items:center;justify-content:space-between;gap:4px}.pk-datagrid thead th .th-content .th-actions{display:flex;align-items:center;gap:2px;flex-shrink:0}.pk-datagrid thead th .th-content .sort-icon-btn{background:none;border:none;padding:1px 2px;cursor:pointer;color:#ccc;border-radius:2px;display:flex;align-items:center;line-height:1}.pk-datagrid thead th .th-content .sort-icon-btn:hover{color:#0072a3;background:#0072a31a}.pk-datagrid thead th .th-content .sort-icon-btn.active{color:#0072a3}.pk-datagrid thead th .th-content .filter-icon-btn{background:none;border:none;padding:1px 2px;cursor:pointer;color:#bbb;border-radius:2px;display:flex;align-items:center;line-height:1}.pk-datagrid thead th .th-content .filter-icon-btn:hover{color:#0072a3;background:#0072a31a}.pk-datagrid thead th .th-content .filter-icon-btn.active{color:#0072a3}.pk-datagrid thead th .filter-popup{position:fixed;background:#fff;border:1px solid #0072a3;border-radius:5px;padding:8px;box-shadow:0 4px 14px #00000026;z-index:9999;min-width:180px}.pk-datagrid thead th .filter-popup input{width:100%;box-sizing:border-box;padding:5px 8px;border:1px solid #b8cfe0;border-radius:3px;outline:none}.pk-datagrid thead th .filter-popup input:focus{border-color:#0072a3;box-shadow:0 0 0 2px #0072a32e}.pk-datagrid thead th .resize-handle{position:absolute;top:0;right:0;width:5px;height:100%;cursor:col-resize;z-index:20}.pk-datagrid thead th .resize-handle:after{content:\"\";position:absolute;top:15%;right:2px;width:2px;height:70%;background:transparent;border-radius:1px;transition:background .15s}.pk-datagrid thead th .resize-handle:hover:after{background:#0072a3}.pk-datagrid tbody tr.pk-dg-row{border:1px solid #cccccc;transition:background-color .15s}.pk-datagrid tbody tr.pk-dg-row:hover{background:#f0f9ff;cursor:pointer}.pk-datagrid tbody tr.pk-dg-row-detail,.pk-datagrid tbody tr.pk-dg-row-detail:hover{background:#fafafa}.pk-datagrid tbody tr.pk-dg-row-detail td{padding:15px;border:1px solid #cccccc}.pk-datagrid .pk-dg-expand-th{width:32px;min-width:32px;padding:0}.pk-datagrid .pk-dg-expand-th.pk-dg-no-detail{width:0;min-width:0;max-width:0;padding:0;border:none;overflow:hidden}\n"] }]
971
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { columns: [{
972
+ type: ContentChildren,
973
+ args: [PkDgColumnComponent]
974
+ }], pagination: [{
975
+ type: ContentChild,
976
+ args: [PkDgPaginationComponent]
977
+ }], rowDetails: [{
978
+ type: ContentChildren,
979
+ args: [PkDgRowDetailComponent, { descendants: true }]
980
+ }], expandDirectives: [{
981
+ type: ContentChildren,
982
+ args: [PkIfExpandedDirective, { descendants: true }]
983
+ }], pkDgLoading: [{
984
+ type: Input
985
+ }], items: [{
986
+ type: Input
987
+ }], filterValues: [{
988
+ type: Input
989
+ }], pkDgRefresh: [{
990
+ type: Output
991
+ }], filterChange: [{
992
+ type: Output
993
+ }], onDocumentClick: [{
994
+ type: HostListener,
995
+ args: ['document:click']
996
+ }] } });
997
+
998
+ class PkDgCellComponent {
999
+ set nowrap(value) {
1000
+ this._nowrap = value;
1001
+ }
1002
+ get nowrap() { return this._nowrap; }
1003
+ _nowrap = true;
1004
+ tdStyle = null;
1005
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1006
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: PkDgCellComponent, isStandalone: false, selector: "pk-dg-cell", inputs: { nowrap: "nowrap", tdStyle: "tdStyle" }, host: { styleAttribute: "display: contents" }, ngImport: i0, template: '<td [class.wrap]="!_nowrap" [ngStyle]="tdStyle"><ng-content></ng-content></td>', isInline: true, styles: [":host{display:contents}:host td{padding:8px;color:#333;border:1px solid #e8e8e8;vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host td.wrap{white-space:normal;overflow:visible;text-overflow:unset}\n"], dependencies: [{ kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
1007
+ }
1008
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgCellComponent, decorators: [{
1009
+ type: Component,
1010
+ args: [{ selector: 'pk-dg-cell', template: '<td [class.wrap]="!_nowrap" [ngStyle]="tdStyle"><ng-content></ng-content></td>', standalone: false, host: {
1011
+ 'style': 'display: contents'
1012
+ }, styles: [":host{display:contents}:host td{padding:8px;color:#333;border:1px solid #e8e8e8;vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host td.wrap{white-space:normal;overflow:visible;text-overflow:unset}\n"] }]
1013
+ }], propDecorators: { nowrap: [{
1014
+ type: Input
1015
+ }], tdStyle: [{
1016
+ type: Input
1017
+ }] } });
1018
+
1019
+ class PkDgFooterComponent {
1020
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1021
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: PkDgFooterComponent, isStandalone: false, selector: "pk-dg-footer", ngImport: i0, template: '<div class="pk-dg-footer"><ng-content></ng-content></div>', isInline: true, styles: [".pk-dg-footer{border-top:2px solid #e0e0e0;background:#fafafa}\n"] });
1022
+ }
1023
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDgFooterComponent, decorators: [{
1024
+ type: Component,
1025
+ args: [{ selector: 'pk-dg-footer', template: '<div class="pk-dg-footer"><ng-content></ng-content></div>', standalone: false, styles: [".pk-dg-footer{border-top:2px solid #e0e0e0;background:#fafafa}\n"] }]
1026
+ }] });
1027
+
1028
+ class PkDatagridModule {
1029
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDatagridModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1030
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: PkDatagridModule, declarations: [PkDatagridComponent,
1031
+ PkDgColumnComponent,
1032
+ PkDgRowComponent,
1033
+ PkDgCellComponent,
1034
+ PkDgFooterComponent,
1035
+ PkDgPaginationComponent,
1036
+ PkDgPageSizeComponent,
1037
+ PkDgRowDetailComponent,
1038
+ PkDgItemsDirective,
1039
+ PkIfExpandedDirective,
1040
+ NowrapDirective], imports: [CommonModule,
1041
+ FormsModule,
1042
+ PkIcon], exports: [PkDatagridComponent,
1043
+ PkDgColumnComponent,
1044
+ PkDgRowComponent,
1045
+ PkDgCellComponent,
1046
+ PkDgFooterComponent,
1047
+ PkDgPaginationComponent,
1048
+ PkDgPageSizeComponent,
1049
+ PkDgRowDetailComponent,
1050
+ PkDgItemsDirective,
1051
+ PkIfExpandedDirective,
1052
+ NowrapDirective,
1053
+ PkIcon] });
1054
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDatagridModule, imports: [CommonModule,
1055
+ FormsModule] });
1056
+ }
1057
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDatagridModule, decorators: [{
1058
+ type: NgModule,
1059
+ args: [{
1060
+ declarations: [
1061
+ PkDatagridComponent,
1062
+ PkDgColumnComponent,
1063
+ PkDgRowComponent,
1064
+ PkDgCellComponent,
1065
+ PkDgFooterComponent,
1066
+ PkDgPaginationComponent,
1067
+ PkDgPageSizeComponent,
1068
+ PkDgRowDetailComponent,
1069
+ PkDgItemsDirective,
1070
+ PkIfExpandedDirective,
1071
+ NowrapDirective
1072
+ ],
1073
+ imports: [
1074
+ CommonModule,
1075
+ FormsModule,
1076
+ PkIcon
1077
+ ],
1078
+ exports: [
1079
+ PkDatagridComponent,
1080
+ PkDgColumnComponent,
1081
+ PkDgRowComponent,
1082
+ PkDgCellComponent,
1083
+ PkDgFooterComponent,
1084
+ PkDgPaginationComponent,
1085
+ PkDgPageSizeComponent,
1086
+ PkDgRowDetailComponent,
1087
+ PkDgItemsDirective,
1088
+ PkIfExpandedDirective,
1089
+ NowrapDirective,
1090
+ PkIcon
1091
+ ]
1092
+ }]
1093
+ }] });
1094
+
1095
+ class DatepickerService {
1096
+ activeDatepicker = new Subject();
1097
+ // Notify when a datepicker is opened
1098
+ openDatepicker(component) {
1099
+ this.activeDatepicker.next(component);
1100
+ }
1101
+ // Subscribe to know when another datepicker opens
1102
+ getActiveDatepicker() {
1103
+ return this.activeDatepicker.asObservable();
1104
+ }
1105
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DatepickerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1106
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DatepickerService, providedIn: 'root' });
1107
+ }
1108
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DatepickerService, decorators: [{
1109
+ type: Injectable,
1110
+ args: [{
1111
+ providedIn: 'root'
1112
+ }]
1113
+ }] });
1114
+
1115
+ class HolidayService {
1116
+ constructor() { }
1117
+ // Mock for testing
1118
+ getHolidays(year) {
1119
+ const mockHolidays = [
1120
+ { date: new Date(2025, 0, 1), title: "วันขึ้นปีใหม่" },
1121
+ { date: new Date(2025, 3, 13), title: "วันสงกรานต์" },
1122
+ { date: new Date(2025, 3, 14), title: "วันสงกรานต์" },
1123
+ { date: new Date(2025, 3, 15), title: "ชดเชยวันสงกรานต์" },
1124
+ { date: new Date(2025, 3, 16), title: "ชดเชยวันสงกรานต์" },
1125
+ { date: new Date(2025, 11, 25), title: "วันคริสต์มาส" }
1126
+ ].filter(h => h.date.getFullYear() === year);
1127
+ return of(mockHolidays);
1128
+ }
1129
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HolidayService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1130
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HolidayService, providedIn: 'root' });
1131
+ }
1132
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HolidayService, decorators: [{
1133
+ type: Injectable,
1134
+ args: [{
1135
+ providedIn: 'root'
1136
+ }]
1137
+ }], ctorParameters: () => [] });
1138
+
1139
+ /*
1140
+ pk-datepicker เป็น component เพื่อใช้แทนการ input type="date"
1141
+
1142
+ การใช้งาน:
1143
+ <pk-datepicker [..] ..></pk-datepicker>
1144
+
1145
+ input parameter:
1146
+ disabled: true | false (default false) - กรณีไม่ต้องการให้เลือกวันที่
1147
+ setNull: true | false (default true) - กรณีไม่ต้องการให้เลือกวันที่ ให้ set เป็น null
1148
+ dateInput: true | false (default true) - กรณีต้องการให้ป้อนตัวเลขวันที่ได้
1149
+ locale: TH | EN (default TH)
1150
+ startOfWeek: 'monday' | 'sunday' = 'monday'
1151
+ minDate: Date | null - กำหนดช่วงวันที่ที่สามารถเลือกได้
1152
+ maxDate: Date | null - กำหนดช่วงวันที่ที่สามารถเลือกได้
1153
+ [(ngModel)]="dateInput: Date" - กำหนดค่าเริ่มต้นของวันที่
1154
+ style= "width: 140px;"
1155
+
1156
+ Output
1157
+ (onDateChange): Date
1158
+
1159
+ Feature:
1160
+ 1.ปีที่เลือกได้ กรณีไม่ได้ระบุ minDate จะเลือกย้อนหลังได้ -20 ปี, หากไม่ได้ระบุ maxDate จะเลือกไปข้างหน้าได้ +10 ปี
1161
+ 2.ระบบจะแสดงวันหยุดได้โดยใช้ค่าจาก mainService.getHoliday
1162
+ */
1163
+ class PkDatepickerComponent {
1164
+ elementRef;
1165
+ datepickerService;
1166
+ holidayService;
1167
+ disabled = false;
1168
+ setNull = true;
1169
+ dateInput = true;
1170
+ locale = 'TH';
1171
+ minDate = null;
1172
+ maxDate = null;
1173
+ startOfWeek = 'monday';
1174
+ style = null;
1175
+ placeholder = '';
1176
+ onDateChange = new EventEmitter();
1177
+ holidays = [];
1178
+ innerValue = null;
1179
+ isOpen = false;
1180
+ currentMonth = new Date();
1181
+ daysInMonth = [];
1182
+ weekdays = [];
1183
+ months = [];
1184
+ years = [];
1185
+ selectedMonth = new Date().getMonth();
1186
+ selectedYear = new Date().getFullYear();
1187
+ dropdownStyles = {};
1188
+ onChange = () => { };
1189
+ onTouched = () => { };
1190
+ subscription;
1191
+ get localeMode() {
1192
+ return String(this.locale || '').trim().toUpperCase() === 'TH' ? 'TH' : 'EN';
1193
+ }
1194
+ constructor(elementRef, datepickerService, holidayService) {
1195
+ this.elementRef = elementRef;
1196
+ this.datepickerService = datepickerService;
1197
+ this.holidayService = holidayService;
1198
+ }
1199
+ ngOnInit() {
1200
+ this.currentMonth = this.innerValue ? new Date(this.innerValue) : new Date();
1201
+ this.currentMonth.setDate(1);
1202
+ this.adjustCurrentMonthToRange();
1203
+ this.selectedMonth = this.currentMonth.getMonth();
1204
+ this.selectedYear = this.localeMode === 'TH'
1205
+ ? this.currentMonth.getFullYear() + 543
1206
+ : this.currentMonth.getFullYear();
1207
+ this.fetchHolidays(this.currentMonth.getFullYear());
1208
+ this.updateCalendar();
1209
+ this.generateMonths();
1210
+ this.generateYears();
1211
+ this.subscription = this.datepickerService.getActiveDatepicker().subscribe(activeComponent => {
1212
+ if (activeComponent !== this && this.isOpen) {
1213
+ this.isOpen = false;
1214
+ }
1215
+ });
1216
+ }
1217
+ ngOnDestroy() {
1218
+ if (this.subscription) {
1219
+ this.subscription.unsubscribe();
1220
+ }
1221
+ }
1222
+ onClickOutside(event) {
1223
+ if (this.isOpen && !this.elementRef.nativeElement.contains(event.target)) {
1224
+ this.isOpen = false;
1225
+ }
1226
+ }
1227
+ onWindowResize() {
1228
+ if (this.isOpen) {
1229
+ this.updateDropdownPosition();
1230
+ }
1231
+ }
1232
+ onWindowScroll() {
1233
+ if (this.isOpen) {
1234
+ this.updateDropdownPosition();
1235
+ }
1236
+ }
1237
+ get value() {
1238
+ return this.innerValue;
1239
+ }
1240
+ set value(val) {
1241
+ if (val !== this.innerValue && this.isDateInRange(val)) {
1242
+ this.innerValue = val;
1243
+ this.onChange(val);
1244
+ this.onDateChange.emit(val);
1245
+ this.currentMonth = new Date(val || new Date());
1246
+ this.currentMonth.setDate(1);
1247
+ this.adjustCurrentMonthToRange();
1248
+ this.selectedMonth = this.currentMonth.getMonth();
1249
+ this.selectedYear = this.localeMode === 'TH'
1250
+ ? this.currentMonth.getFullYear() + 543
1251
+ : this.currentMonth.getFullYear();
1252
+ this.fetchHolidays(this.currentMonth.getFullYear());
1253
+ this.updateCalendar();
1254
+ }
1255
+ }
1256
+ get formattedDate() {
1257
+ if (!this.innerValue)
1258
+ return '';
1259
+ const options = {
1260
+ day: '2-digit',
1261
+ month: '2-digit',
1262
+ year: 'numeric'
1263
+ };
1264
+ return this.localeMode === 'TH'
1265
+ ? this.innerValue.toLocaleDateString('th-TH', { ...options, year: 'numeric' })
1266
+ : this.innerValue.toLocaleDateString('en-US', options);
1267
+ }
1268
+ get currentMonthYear() {
1269
+ const options = {
1270
+ month: 'long',
1271
+ year: 'numeric'
1272
+ };
1273
+ return this.localeMode === 'TH'
1274
+ ? this.currentMonth.toLocaleDateString('th-TH', options)
1275
+ : this.currentMonth.toLocaleDateString('en-US', options);
1276
+ }
1277
+ get isTodayDisabled() {
1278
+ return this.disabled || !this.isDateInRange(new Date());
1279
+ }
1280
+ get canGoPrevMonth() {
1281
+ if (this.disabled || !this.minDate)
1282
+ return true;
1283
+ const prevMonth = new Date(this.currentMonth);
1284
+ prevMonth.setMonth(prevMonth.getMonth() - 1);
1285
+ return prevMonth.getTime() >= this.minDate.getTime();
1286
+ }
1287
+ get canGoNextMonth() {
1288
+ if (this.disabled || !this.maxDate)
1289
+ return true;
1290
+ const nextMonth = new Date(this.currentMonth);
1291
+ nextMonth.setMonth(nextMonth.getMonth() + 1);
1292
+ return nextMonth.getTime() <= this.maxDate.getTime();
1293
+ }
1294
+ updateCalendar() {
1295
+ this.currentMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth(), 1);
1296
+ if (this.startOfWeek === 'monday') {
1297
+ this.weekdays = this.localeMode === 'TH'
1298
+ ? ['จ', 'อ', 'พ', 'พฤ', 'ศ', 'ส', 'อา']
1299
+ : ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
1300
+ }
1301
+ else {
1302
+ this.weekdays = this.localeMode === 'TH'
1303
+ ? ['อา', 'จ', 'อ', 'พ', 'พฤ', 'ศ', 'ส']
1304
+ : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
1305
+ }
1306
+ this.daysInMonth = [];
1307
+ const firstDayOfMonth = this.currentMonth.getDay();
1308
+ const daysInMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth() + 1, 0).getDate();
1309
+ const startOffset = this.startOfWeek === 'monday' ? (firstDayOfMonth + 6) % 7 : firstDayOfMonth;
1310
+ for (let i = startOffset - 1; i >= 0; i--) {
1311
+ const prevDate = new Date(this.currentMonth);
1312
+ prevDate.setDate(this.currentMonth.getDate() - i - 1);
1313
+ this.daysInMonth.push(prevDate);
1314
+ }
1315
+ for (let i = 1; i <= daysInMonth; i++) {
1316
+ const newDate = new Date(this.currentMonth);
1317
+ newDate.setDate(i);
1318
+ this.daysInMonth.push(newDate);
1319
+ }
1320
+ const totalCells = Math.ceil((startOffset + daysInMonth) / 7) * 7;
1321
+ for (let i = daysInMonth + 1; this.daysInMonth.length < totalCells; i++) {
1322
+ const nextDate = new Date(this.currentMonth);
1323
+ nextDate.setDate(i);
1324
+ this.daysInMonth.push(nextDate);
1325
+ }
1326
+ }
1327
+ generateMonths() {
1328
+ this.months = Array.from({ length: 12 }, (_, i) => {
1329
+ const date = new Date(2000, i, 1);
1330
+ return {
1331
+ name: date.toLocaleString(this.localeMode === 'TH' ? 'th-TH' : 'en-US', { month: 'long' }),
1332
+ value: i
1333
+ };
1334
+ });
1335
+ }
1336
+ generateYears() {
1337
+ const currentYear = new Date().getFullYear();
1338
+ let minYear;
1339
+ let maxYear;
1340
+ if (this.minDate && this.maxDate) {
1341
+ minYear = this.minDate.getFullYear();
1342
+ maxYear = this.maxDate.getFullYear();
1343
+ }
1344
+ else {
1345
+ minYear = currentYear - 20;
1346
+ maxYear = currentYear + 10;
1347
+ }
1348
+ this.years = [];
1349
+ for (let i = minYear; i <= maxYear; i++) {
1350
+ this.years.push(this.localeMode === 'TH' ? i + 543 : i);
1351
+ }
1352
+ }
1353
+ fetchHolidays(year) {
1354
+ // No adjustment needed, year is already Gregorian
1355
+ this.holidayService.getHolidays(year).subscribe(holidays => {
1356
+ this.holidays = holidays;
1357
+ this.updateCalendar();
1358
+ });
1359
+ }
1360
+ onMonthChange(month) {
1361
+ this.selectedMonth = month;
1362
+ this.currentMonth = new Date(this.currentMonth.getFullYear(), month, 1);
1363
+ this.adjustCurrentMonthToRange();
1364
+ this.selectedYear = this.localeMode === 'TH'
1365
+ ? this.currentMonth.getFullYear() + 543
1366
+ : this.currentMonth.getFullYear();
1367
+ this.fetchHolidays(this.currentMonth.getFullYear());
1368
+ }
1369
+ onYearChange(year) {
1370
+ this.selectedYear = year;
1371
+ const adjustedYear = this.localeMode === 'TH' ? year - 543 : year;
1372
+ this.currentMonth = new Date(adjustedYear, this.currentMonth.getMonth(), 1);
1373
+ this.adjustCurrentMonthToRange();
1374
+ this.fetchHolidays(adjustedYear);
1375
+ }
1376
+ onChangeDate(event) {
1377
+ let inputValue = event.target.value;
1378
+ // แปลงปี พ.ศ. เป็น ค.ศ. ถ้าเป็น locale TH
1379
+ if (this.localeMode === 'TH' && inputValue) {
1380
+ // ตรวจสอบรูปแบบ DD/MM/YYYY (TH format)
1381
+ const thaiDatePattern = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/;
1382
+ const match = inputValue.match(thaiDatePattern);
1383
+ if (match) {
1384
+ const day = match[1];
1385
+ const month = match[2];
1386
+ const year = parseInt(match[3]);
1387
+ // แปลง พ.ศ. เป็น ค.ศ. ถ้าปีมากกว่า 2400
1388
+ if (year > 2400) {
1389
+ inputValue = `${year - 543}-${month}-${day}`;
1390
+ }
1391
+ }
1392
+ }
1393
+ const date = new Date(inputValue);
1394
+ if (!date || isNaN(date.getTime())) {
1395
+ return;
1396
+ }
1397
+ if (this.isDateInRange(date)) {
1398
+ // this.onDateChange.emit(date);
1399
+ this.value = date;
1400
+ // this.currentMonth = new Date(date.getFullYear(), date.getMonth(), 1);
1401
+ // this.selectedMonth = this.currentMonth.getMonth();
1402
+ // this.selectedYear = this.locale === 'TH'
1403
+ // ? this.currentMonth.getFullYear() + 543
1404
+ // : this.currentMonth.getFullYear();
1405
+ // this.fetchHolidays(this.currentMonth.getFullYear());
1406
+ // this.updateCalendar();
1407
+ this.isOpen = false;
1408
+ this.onTouched();
1409
+ }
1410
+ else {
1411
+ event.target.value = this.formattedDate; // Reset to formatted date if out of range
1412
+ }
1413
+ }
1414
+ resetDate() {
1415
+ this.value = null;
1416
+ this.isOpen = false;
1417
+ this.onTouched();
1418
+ }
1419
+ prevMonth() {
1420
+ if (this.canGoPrevMonth) {
1421
+ this.currentMonth.setMonth(this.currentMonth.getMonth() - 1);
1422
+ this.selectedMonth = this.currentMonth.getMonth();
1423
+ this.selectedYear = this.localeMode === 'TH'
1424
+ ? this.currentMonth.getFullYear() + 543
1425
+ : this.currentMonth.getFullYear();
1426
+ this.adjustCurrentMonthToRange();
1427
+ this.fetchHolidays(this.currentMonth.getFullYear());
1428
+ }
1429
+ }
1430
+ nextMonth() {
1431
+ if (this.canGoNextMonth) {
1432
+ this.currentMonth.setMonth(this.currentMonth.getMonth() + 1);
1433
+ this.selectedMonth = this.currentMonth.getMonth();
1434
+ this.selectedYear = this.localeMode === 'TH'
1435
+ ? this.currentMonth.getFullYear() + 543
1436
+ : this.currentMonth.getFullYear();
1437
+ this.adjustCurrentMonthToRange();
1438
+ this.fetchHolidays(this.currentMonth.getFullYear());
1439
+ }
1440
+ }
1441
+ selectToday() {
1442
+ const today = new Date();
1443
+ if (this.isDateInRange(today)) {
1444
+ this.value = today;
1445
+ this.currentMonth = new Date(today.getFullYear(), today.getMonth(), 1);
1446
+ this.selectedMonth = this.currentMonth.getMonth();
1447
+ this.selectedYear = this.localeMode === 'TH'
1448
+ ? this.currentMonth.getFullYear() + 543
1449
+ : this.currentMonth.getFullYear();
1450
+ this.fetchHolidays(this.currentMonth.getFullYear());
1451
+ this.updateCalendar();
1452
+ this.isOpen = false;
1453
+ this.onTouched();
1454
+ }
1455
+ }
1456
+ writeValue(value) {
1457
+ if (this.isDateInRange(value)) {
1458
+ this.innerValue = value;
1459
+ if (value) {
1460
+ this.currentMonth = new Date(value.getFullYear(), value.getMonth(), 1);
1461
+ }
1462
+ else {
1463
+ this.currentMonth = new Date();
1464
+ this.currentMonth.setDate(1);
1465
+ this.adjustCurrentMonthToRange();
1466
+ }
1467
+ this.selectedMonth = this.currentMonth.getMonth();
1468
+ this.selectedYear = this.localeMode === 'TH'
1469
+ ? this.currentMonth.getFullYear() + 543
1470
+ : this.currentMonth.getFullYear();
1471
+ this.fetchHolidays(this.currentMonth.getFullYear());
1472
+ }
1473
+ }
1474
+ registerOnChange(fn) {
1475
+ this.onChange = fn;
1476
+ }
1477
+ registerOnTouched(fn) {
1478
+ this.onTouched = fn;
1479
+ }
1480
+ setDisabledState(isDisabled) {
1481
+ this.disabled = isDisabled;
1482
+ }
1483
+ onDateSelect(date) {
1484
+ if (this.isDateInRange(date)) {
1485
+ this.value = date;
1486
+ this.isOpen = false;
1487
+ this.onTouched();
1488
+ }
1489
+ }
1490
+ togglePicker(event) {
1491
+ if (!this.disabled) {
1492
+ if (!this.isOpen) {
1493
+ this.datepickerService.openDatepicker(this);
1494
+ }
1495
+ this.isOpen = !this.isOpen;
1496
+ if (this.isOpen) {
1497
+ setTimeout(() => this.updateDropdownPosition(), 0);
1498
+ }
1499
+ event.stopPropagation();
1500
+ }
1501
+ }
1502
+ updateDropdownPosition() {
1503
+ const inputEl = this.elementRef.nativeElement.querySelector('.datepicker-input');
1504
+ if (!inputEl)
1505
+ return;
1506
+ const rect = inputEl.getBoundingClientRect();
1507
+ const margin = 8;
1508
+ const dropdownWidth = 270;
1509
+ const dropdownHeight = 360;
1510
+ let left = rect.left;
1511
+ let top = rect.bottom + 4;
1512
+ if (left + dropdownWidth + margin > window.innerWidth) {
1513
+ left = window.innerWidth - dropdownWidth - margin;
1514
+ }
1515
+ if (left < margin) {
1516
+ left = margin;
1517
+ }
1518
+ if (top + dropdownHeight + margin > window.innerHeight && rect.top - dropdownHeight > margin) {
1519
+ top = rect.top - dropdownHeight - 4;
1520
+ }
1521
+ if (top < margin) {
1522
+ top = margin;
1523
+ }
1524
+ this.dropdownStyles = {
1525
+ position: 'fixed',
1526
+ top: `${top}px`,
1527
+ left: `${left}px`,
1528
+ zIndex: '2147483647'
1529
+ };
1530
+ }
1531
+ isSelected(date) {
1532
+ return this.innerValue?.toDateString() === date.toDateString();
1533
+ }
1534
+ isCurrentMonth(date) {
1535
+ return date.getMonth() === this.currentMonth.getMonth() &&
1536
+ date.getFullYear() === this.currentMonth.getFullYear();
1537
+ }
1538
+ isDateInRange(date) {
1539
+ if (!date)
1540
+ return true;
1541
+ const time = date.getTime();
1542
+ const minTime = this.minDate
1543
+ ? new Date(this.minDate.getFullYear(), this.minDate.getMonth(), this.minDate.getDate(), 0, 0, 0, 0).getTime()
1544
+ : null;
1545
+ const maxTime = this.maxDate
1546
+ ? new Date(this.maxDate.getFullYear(), this.maxDate.getMonth(), this.maxDate.getDate(), 23, 59, 59, 999).getTime()
1547
+ : null;
1548
+ return (minTime === null || time >= minTime) &&
1549
+ (maxTime === null || time <= maxTime);
1550
+ }
1551
+ isMonthDisabled(month) {
1552
+ if (!this.minDate && !this.maxDate)
1553
+ return false;
1554
+ const year = this.localeMode === 'TH' ? this.selectedYear - 543 : this.selectedYear;
1555
+ const startOfMonth = new Date(year, month, 1).getTime();
1556
+ const endOfMonth = new Date(year, month + 1, 0).getTime();
1557
+ const beforeMin = this.minDate ? endOfMonth < this.minDate.getTime() : false;
1558
+ const afterMax = this.maxDate ? startOfMonth > this.maxDate.getTime() : false;
1559
+ return beforeMin || afterMax;
1560
+ }
1561
+ isWeekend(date) {
1562
+ return date.getDay() === 0 || date.getDay() === 6;
1563
+ }
1564
+ isExplicitHoliday(date) {
1565
+ return this.holidays.some(holiday => holiday.date.getFullYear() === date.getFullYear() &&
1566
+ holiday.date.getMonth() === date.getMonth() &&
1567
+ holiday.date.getDate() === date.getDate());
1568
+ }
1569
+ getHolidayTitle(date) {
1570
+ const holiday = this.holidays.find(h => h.date.getFullYear() === date.getFullYear() &&
1571
+ h.date.getMonth() === date.getMonth() &&
1572
+ h.date.getDate() === date.getDate());
1573
+ return holiday ? holiday.title : '';
1574
+ }
1575
+ adjustCurrentMonthToRange() {
1576
+ if (this.minDate && this.currentMonth.getTime() < this.minDate.getTime()) {
1577
+ this.currentMonth = new Date(this.minDate);
1578
+ this.currentMonth.setDate(1);
1579
+ }
1580
+ if (this.maxDate && this.currentMonth.getTime() > this.maxDate.getTime()) {
1581
+ this.currentMonth = new Date(this.maxDate);
1582
+ this.currentMonth.setDate(1);
1583
+ }
1584
+ }
1585
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDatepickerComponent, deps: [{ token: i0.ElementRef }, { token: DatepickerService }, { token: HolidayService }], target: i0.ɵɵFactoryTarget.Component });
1586
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkDatepickerComponent, isStandalone: true, selector: "pk-datepicker", inputs: { disabled: "disabled", setNull: "setNull", dateInput: "dateInput", locale: "locale", minDate: "minDate", maxDate: "maxDate", startOfWeek: "startOfWeek", style: "style", placeholder: "placeholder" }, outputs: { onDateChange: "onDateChange" }, host: { listeners: { "document:click": "onClickOutside($event)", "window:resize": "onWindowResize()", "window:scroll": "onWindowScroll()" } }, providers: [
1587
+ {
1588
+ provide: NG_VALUE_ACCESSOR,
1589
+ useExisting: forwardRef(() => PkDatepickerComponent),
1590
+ multi: true
1591
+ }
1592
+ ], ngImport: i0, template: "<div class=\"datepicker-container\" [class.datepicker-container--open]=\"isOpen\">\n <input type=\"text\" [value]=\"formattedDate\" [disabled]=\"disabled\" [style]=\"style\"\n [placeholder]=\"placeholder? placeholder : (localeMode === 'TH' ? '\u0E27\u0E27/\u0E14\u0E14/\u0E1B\u0E1B\u0E1B\u0E1B' : 'DD/MM/YYYY')\"\n [title]=\"placeholder? placeholder : (localeMode === 'TH' ? 'Click \u0E40\u0E1E\u0E37\u0E48\u0E2D\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E27\u0E31\u0E19\u0E17\u0E35\u0E48' : 'Click for select date')\"\n (click)=\"togglePicker($event)\" class=\"datepicker-input\" [readonly]=\"!dateInput\" (change)=\"onChangeDate($event)\" />\n @if (isOpen) {\n <div class=\"datepicker-dropdown\" [ngStyle]=\"dropdownStyles\">\n <div class=\"calendar\">\n <div class=\"calendar-header\">\n <button class=\"nav-btn\" (click)=\"prevMonth()\" [disabled]=\"!canGoPrevMonth\">\n << </button>\n <select [(ngModel)]=\"selectedMonth\" (ngModelChange)=\"onMonthChange($event)\" [disabled]=\"disabled\">\n @for (month of months; track month.value) {\n <option [value]=\"month.value\" [disabled]=\"isMonthDisabled(month.value)\">{{ month.name }}</option>\n }\n </select>\n <select [(ngModel)]=\"selectedYear\" (ngModelChange)=\"onYearChange($event)\" [disabled]=\"disabled\">\n @for (year of years; track year) {\n <option [value]=\"year\">{{ year }}</option>\n }\n </select>\n <button class=\"nav-btn\" (click)=\"nextMonth()\" [disabled]=\"!canGoNextMonth\">>></button>\n </div>\n\n <div class=\"month-year-display\">{{ currentMonthYear }}</div>\n\n <div class=\"weekdays\">\n @for (weekday of weekdays; track weekday) {\n <div class=\"weekday\">{{ weekday }}</div>\n }\n </div>\n\n <div class=\"days\">\n @for (day of daysInMonth; track day) {\n <button\n [class.selected]=\"isSelected(day)\"\n [class.other-month]=\"!isCurrentMonth(day)\"\n [class.weekend]=\"isWeekend(day)\"\n [class.holiday]=\"isExplicitHoliday(day)\"\n [disabled]=\"disabled || !isDateInRange(day)\"\n [title]=\"isExplicitHoliday(day) ? getHolidayTitle(day) : ''\"\n (click)=\"onDateSelect(day)\">\n {{ day.getDate() }}\n </button>\n }\n </div>\n\n <div class=\"calendar-footer\">\n <button class=\"today-btn\" (click)=\"selectToday()\" [disabled]=\"isTodayDisabled\">\n {{ localeMode === 'TH' ? '\u0E27\u0E31\u0E19\u0E19\u0E35\u0E49' : 'Today' }}\n </button>\n @if (setNull) {\n <button class=\"reset-btn\" (click)=\"resetDate()\" [disabled]=\"disabled\">\n {{ localeMode === 'TH' ? '\u0E25\u0E1A' : 'Clear' }}\n </button>\n }\n </div>\n </div>\n </div>\n }\n</div>", styles: [".datepicker-container{width:auto;min-width:160px;position:relative;display:inline-block;z-index:auto}.datepicker-container--open{z-index:9500}.datepicker-input{width:100%;min-width:160px;height:35px;padding:8px 12px;border:1px solid #777777;border-radius:4px;cursor:pointer;box-sizing:border-box}.datepicker-input:disabled{background-color:#f5f5f5;cursor:not-allowed}.datepicker-dropdown{position:absolute;top:100%;left:0;z-index:10000;background:#fff;border:1px solid #ccc;border-radius:4px;box-shadow:0 2px 4px #0000001a}.calendar{padding:10px;min-width:250px}.calendar-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;gap:5px}.calendar-header select{padding:5px;border:1px solid #ccc;border-radius:4px;background:#fff;cursor:pointer;flex:1;font-size:14px;height:30px;box-sizing:border-box}.calendar-header select:disabled{background-color:#f5f5f5;cursor:not-allowed}.calendar-header select option:disabled{color:#999;background-color:#f5f5f5}.nav-btn{padding:3px 8px;font-size:14px;height:30px;background:#f0f0f0;border:1px solid #ccc;border-radius:4px;cursor:pointer;box-sizing:border-box}.nav-btn:hover:not(:disabled){background:#e0e0e0}.nav-btn:disabled{cursor:not-allowed;opacity:.6}.month-year-display{text-align:center;font-size:14px;font-weight:700;margin-bottom:5px;color:#333;background-color:#d3d3d3;padding:5px;border-radius:4px}.weekdays{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:5px}.weekday{text-align:center;font-size:12px;color:#666}.days{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:10px}.days button{padding:8px;border:none!important;background:none;cursor:pointer;border-radius:50%}.days button:hover:not(.selected):not(:disabled){background:#f0f0f0}.days button.selected{background:#007bff;color:#fff}.days button.other-month{color:#999}.days button.weekend:not(.selected):not(:disabled){color:#e54a02}.days button.holiday:not(.selected):not(:disabled){color:red;font-weight:700}.days button:disabled{cursor:not-allowed;opacity:.6;color:#ccc}.calendar-footer{display:flex;justify-content:center;gap:2px}.today-btn{padding:5px 10px;font-size:12px;background:#f0f0f0;border:1px solid #ccc;border-radius:4px;cursor:pointer}.today-btn:hover:not(:disabled){background:#e0e0e0}.today-btn:disabled{cursor:not-allowed;opacity:.6}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
1593
+ }
1594
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkDatepickerComponent, decorators: [{
1595
+ type: Component,
1596
+ args: [{ selector: 'pk-datepicker', imports: [FormsModule, NgStyle], providers: [
1597
+ {
1598
+ provide: NG_VALUE_ACCESSOR,
1599
+ useExisting: forwardRef(() => PkDatepickerComponent),
1600
+ multi: true
1601
+ }
1602
+ ], standalone: true, template: "<div class=\"datepicker-container\" [class.datepicker-container--open]=\"isOpen\">\n <input type=\"text\" [value]=\"formattedDate\" [disabled]=\"disabled\" [style]=\"style\"\n [placeholder]=\"placeholder? placeholder : (localeMode === 'TH' ? '\u0E27\u0E27/\u0E14\u0E14/\u0E1B\u0E1B\u0E1B\u0E1B' : 'DD/MM/YYYY')\"\n [title]=\"placeholder? placeholder : (localeMode === 'TH' ? 'Click \u0E40\u0E1E\u0E37\u0E48\u0E2D\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E27\u0E31\u0E19\u0E17\u0E35\u0E48' : 'Click for select date')\"\n (click)=\"togglePicker($event)\" class=\"datepicker-input\" [readonly]=\"!dateInput\" (change)=\"onChangeDate($event)\" />\n @if (isOpen) {\n <div class=\"datepicker-dropdown\" [ngStyle]=\"dropdownStyles\">\n <div class=\"calendar\">\n <div class=\"calendar-header\">\n <button class=\"nav-btn\" (click)=\"prevMonth()\" [disabled]=\"!canGoPrevMonth\">\n << </button>\n <select [(ngModel)]=\"selectedMonth\" (ngModelChange)=\"onMonthChange($event)\" [disabled]=\"disabled\">\n @for (month of months; track month.value) {\n <option [value]=\"month.value\" [disabled]=\"isMonthDisabled(month.value)\">{{ month.name }}</option>\n }\n </select>\n <select [(ngModel)]=\"selectedYear\" (ngModelChange)=\"onYearChange($event)\" [disabled]=\"disabled\">\n @for (year of years; track year) {\n <option [value]=\"year\">{{ year }}</option>\n }\n </select>\n <button class=\"nav-btn\" (click)=\"nextMonth()\" [disabled]=\"!canGoNextMonth\">>></button>\n </div>\n\n <div class=\"month-year-display\">{{ currentMonthYear }}</div>\n\n <div class=\"weekdays\">\n @for (weekday of weekdays; track weekday) {\n <div class=\"weekday\">{{ weekday }}</div>\n }\n </div>\n\n <div class=\"days\">\n @for (day of daysInMonth; track day) {\n <button\n [class.selected]=\"isSelected(day)\"\n [class.other-month]=\"!isCurrentMonth(day)\"\n [class.weekend]=\"isWeekend(day)\"\n [class.holiday]=\"isExplicitHoliday(day)\"\n [disabled]=\"disabled || !isDateInRange(day)\"\n [title]=\"isExplicitHoliday(day) ? getHolidayTitle(day) : ''\"\n (click)=\"onDateSelect(day)\">\n {{ day.getDate() }}\n </button>\n }\n </div>\n\n <div class=\"calendar-footer\">\n <button class=\"today-btn\" (click)=\"selectToday()\" [disabled]=\"isTodayDisabled\">\n {{ localeMode === 'TH' ? '\u0E27\u0E31\u0E19\u0E19\u0E35\u0E49' : 'Today' }}\n </button>\n @if (setNull) {\n <button class=\"reset-btn\" (click)=\"resetDate()\" [disabled]=\"disabled\">\n {{ localeMode === 'TH' ? '\u0E25\u0E1A' : 'Clear' }}\n </button>\n }\n </div>\n </div>\n </div>\n }\n</div>", styles: [".datepicker-container{width:auto;min-width:160px;position:relative;display:inline-block;z-index:auto}.datepicker-container--open{z-index:9500}.datepicker-input{width:100%;min-width:160px;height:35px;padding:8px 12px;border:1px solid #777777;border-radius:4px;cursor:pointer;box-sizing:border-box}.datepicker-input:disabled{background-color:#f5f5f5;cursor:not-allowed}.datepicker-dropdown{position:absolute;top:100%;left:0;z-index:10000;background:#fff;border:1px solid #ccc;border-radius:4px;box-shadow:0 2px 4px #0000001a}.calendar{padding:10px;min-width:250px}.calendar-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;gap:5px}.calendar-header select{padding:5px;border:1px solid #ccc;border-radius:4px;background:#fff;cursor:pointer;flex:1;font-size:14px;height:30px;box-sizing:border-box}.calendar-header select:disabled{background-color:#f5f5f5;cursor:not-allowed}.calendar-header select option:disabled{color:#999;background-color:#f5f5f5}.nav-btn{padding:3px 8px;font-size:14px;height:30px;background:#f0f0f0;border:1px solid #ccc;border-radius:4px;cursor:pointer;box-sizing:border-box}.nav-btn:hover:not(:disabled){background:#e0e0e0}.nav-btn:disabled{cursor:not-allowed;opacity:.6}.month-year-display{text-align:center;font-size:14px;font-weight:700;margin-bottom:5px;color:#333;background-color:#d3d3d3;padding:5px;border-radius:4px}.weekdays{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:5px}.weekday{text-align:center;font-size:12px;color:#666}.days{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:10px}.days button{padding:8px;border:none!important;background:none;cursor:pointer;border-radius:50%}.days button:hover:not(.selected):not(:disabled){background:#f0f0f0}.days button.selected{background:#007bff;color:#fff}.days button.other-month{color:#999}.days button.weekend:not(.selected):not(:disabled){color:#e54a02}.days button.holiday:not(.selected):not(:disabled){color:red;font-weight:700}.days button:disabled{cursor:not-allowed;opacity:.6;color:#ccc}.calendar-footer{display:flex;justify-content:center;gap:2px}.today-btn{padding:5px 10px;font-size:12px;background:#f0f0f0;border:1px solid #ccc;border-radius:4px;cursor:pointer}.today-btn:hover:not(:disabled){background:#e0e0e0}.today-btn:disabled{cursor:not-allowed;opacity:.6}\n"] }]
1603
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: DatepickerService }, { type: HolidayService }], propDecorators: { disabled: [{
1604
+ type: Input
1605
+ }], setNull: [{
1606
+ type: Input
1607
+ }], dateInput: [{
1608
+ type: Input
1609
+ }], locale: [{
1610
+ type: Input
1611
+ }], minDate: [{
1612
+ type: Input
1613
+ }], maxDate: [{
1614
+ type: Input
1615
+ }], startOfWeek: [{
1616
+ type: Input
1617
+ }], style: [{
1618
+ type: Input
1619
+ }], placeholder: [{
1620
+ type: Input
1621
+ }], onDateChange: [{
1622
+ type: Output
1623
+ }], onClickOutside: [{
1624
+ type: HostListener,
1625
+ args: ['document:click', ['$event']]
1626
+ }], onWindowResize: [{
1627
+ type: HostListener,
1628
+ args: ['window:resize']
1629
+ }], onWindowScroll: [{
1630
+ type: HostListener,
1631
+ args: ['window:scroll']
1632
+ }] } });
1633
+
1634
+ class PkProgressComponent {
1635
+ config = input({
1636
+ type: 'line',
1637
+ percent: 0,
1638
+ status: 'normal',
1639
+ showInfo: true,
1640
+ strokeWidth: 8,
1641
+ striped: false,
1642
+ animated: false,
1643
+ indeterminate: false,
1644
+ }, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
1645
+ // Circle calculations
1646
+ circleSize = computed(() => {
1647
+ const strokeWidth = this.config().strokeWidth || 6;
1648
+ return 120; // Fixed size for circle
1649
+ }, ...(ngDevMode ? [{ debugName: "circleSize" }] : /* istanbul ignore next */ []));
1650
+ radius = computed(() => {
1651
+ const size = this.circleSize();
1652
+ const strokeWidth = this.config().strokeWidth || 6;
1653
+ return (size - strokeWidth) / 2;
1654
+ }, ...(ngDevMode ? [{ debugName: "radius" }] : /* istanbul ignore next */ []));
1655
+ circumference = computed(() => {
1656
+ return 2 * Math.PI * this.radius();
1657
+ }, ...(ngDevMode ? [{ debugName: "circumference" }] : /* istanbul ignore next */ []));
1658
+ offset = computed(() => {
1659
+ const percent = this.config().percent || 0;
1660
+ return this.circumference() * (1 - percent / 100);
1661
+ }, ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
1662
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkProgressComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1663
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkProgressComponent, isStandalone: true, selector: "pk-progress", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1664
+ @if (config().type === 'circle') {
1665
+ <!-- Circle Progress -->
1666
+ <div class="pk-progress-circle" [style.width.px]="circleSize()" [style.height.px]="circleSize()">
1667
+ <svg [attr.viewBox]="'0 0 ' + circleSize() + ' ' + circleSize()">
1668
+ <!-- Background circle -->
1669
+ <circle
1670
+ class="pk-progress-circle-bg"
1671
+ [attr.cx]="circleSize() / 2"
1672
+ [attr.cy]="circleSize() / 2"
1673
+ [attr.r]="radius()"
1674
+ [attr.stroke-width]="config().strokeWidth || 6"
1675
+ fill="none" />
1676
+
1677
+ <!-- Progress circle -->
1678
+ <circle
1679
+ class="pk-progress-circle-path"
1680
+ [class.pk-progress-success]="config().status === 'success'"
1681
+ [class.pk-progress-error]="config().status === 'error'"
1682
+ [class.pk-progress-warning]="config().status === 'warning'"
1683
+ [attr.cx]="circleSize() / 2"
1684
+ [attr.cy]="circleSize() / 2"
1685
+ [attr.r]="radius()"
1686
+ [attr.stroke-width]="config().strokeWidth || 6"
1687
+ [attr.stroke-dasharray]="circumference()"
1688
+ [attr.stroke-dashoffset]="offset()"
1689
+ [style.stroke]="config().color || null"
1690
+ fill="none" />
1691
+ </svg>
1692
+
1693
+ @if (config().showInfo) {
1694
+ <div class="pk-progress-circle-text">
1695
+ @if (config().status === 'success') {
1696
+ <span class="pk-progress-icon">✓</span>
1697
+ } @else if (config().status === 'error') {
1698
+ <span class="pk-progress-icon">✕</span>
1699
+ } @else {
1700
+ <span>{{ config().percent }}%</span>
1701
+ }
1702
+ </div>
1703
+ }
1704
+ </div>
1705
+ } @else {
1706
+ <!-- Line Progress -->
1707
+ <div class="pk-progress-line">
1708
+ @if (config().label) {
1709
+ <div class="pk-progress-label">{{ config().label }}</div>
1710
+ }
1711
+
1712
+ <div class="pk-progress-outer">
1713
+ <div
1714
+ class="pk-progress-inner"
1715
+ [class.pk-progress-striped]="config().striped"
1716
+ [class.pk-progress-animated]="config().animated"
1717
+ [class.pk-progress-indeterminate]="config().indeterminate"
1718
+ [style.height.px]="config().strokeWidth || 8">
1719
+ <div
1720
+ class="pk-progress-bg"
1721
+ [class.pk-progress-success]="config().status === 'success'"
1722
+ [class.pk-progress-error]="config().status === 'error'"
1723
+ [class.pk-progress-warning]="config().status === 'warning'"
1724
+ [style.width.%]="config().indeterminate ? 30 : config().percent"
1725
+ [style.background]="config().color || null">
1726
+ </div>
1727
+ </div>
1728
+ </div>
1729
+
1730
+ @if (config().showInfo) {
1731
+ <div class="pk-progress-text">
1732
+ @if (config().status === 'success') {
1733
+ <span class="pk-progress-icon">✓</span>
1734
+ } @else if (config().status === 'error') {
1735
+ <span class="pk-progress-icon">✕</span>
1736
+ } @else {
1737
+ {{ config().percent }}%
1738
+ }
1739
+ </div>
1740
+ }
1741
+ </div>
1742
+ }
1743
+ `, isInline: true, styles: [".pk-progress-line{display:flex;align-items:center;gap:.5rem}.pk-progress-label{flex-shrink:0;color:#565656;font-size:.875rem;font-weight:500}.pk-progress-outer{flex:1;background:#e8e8e8;border-radius:100px;overflow:hidden}.pk-progress-inner{width:100%;position:relative;background:transparent;border-radius:100px;overflow:hidden}.pk-progress-bg{height:100%;background:#0072a3;border-radius:100px;transition:width .3s ease}.pk-progress-success{background:#00a651!important}.pk-progress-error{background:#e62700!important}.pk-progress-warning{background:#ffc600!important}.pk-progress-striped .pk-progress-bg{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.pk-progress-animated .pk-progress-bg{animation:progress-stripes 1s linear infinite}@keyframes progress-stripes{0%{background-position:1rem 0}to{background-position:0 0}}.pk-progress-indeterminate .pk-progress-bg{animation:progress-indeterminate 2s linear infinite}@keyframes progress-indeterminate{0%{margin-left:0}50%{margin-left:70%}to{margin-left:0}}.pk-progress-text{flex-shrink:0;min-width:2.5rem;text-align:right;color:#565656;font-size:.875rem}.pk-progress-circle{position:relative;display:inline-flex;align-items:center;justify-content:center}.pk-progress-circle svg{transform:rotate(-90deg)}.pk-progress-circle-bg{stroke:#e8e8e8}.pk-progress-circle-path{stroke:#0072a3;stroke-linecap:round;transition:stroke-dashoffset .3s ease}.pk-progress-circle-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#565656;font-size:1rem;font-weight:600}.pk-progress-icon{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;border-radius:50%;font-size:.75rem;font-weight:700}.pk-progress-success+.pk-progress-text .pk-progress-icon,.pk-progress-circle.pk-progress-success .pk-progress-icon{color:#fff;background:#00a651}.pk-progress-error+.pk-progress-text .pk-progress-icon,.pk-progress-circle.pk-progress-error .pk-progress-icon{color:#fff;background:#e62700}.pk-progress-warning+.pk-progress-text .pk-progress-icon,.pk-progress-circle.pk-progress-warning .pk-progress-icon{color:#fff;background:#ffc600}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1744
+ }
1745
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkProgressComponent, decorators: [{
1746
+ type: Component,
1747
+ args: [{ selector: 'pk-progress', standalone: true, imports: [CommonModule], template: `
1748
+ @if (config().type === 'circle') {
1749
+ <!-- Circle Progress -->
1750
+ <div class="pk-progress-circle" [style.width.px]="circleSize()" [style.height.px]="circleSize()">
1751
+ <svg [attr.viewBox]="'0 0 ' + circleSize() + ' ' + circleSize()">
1752
+ <!-- Background circle -->
1753
+ <circle
1754
+ class="pk-progress-circle-bg"
1755
+ [attr.cx]="circleSize() / 2"
1756
+ [attr.cy]="circleSize() / 2"
1757
+ [attr.r]="radius()"
1758
+ [attr.stroke-width]="config().strokeWidth || 6"
1759
+ fill="none" />
1760
+
1761
+ <!-- Progress circle -->
1762
+ <circle
1763
+ class="pk-progress-circle-path"
1764
+ [class.pk-progress-success]="config().status === 'success'"
1765
+ [class.pk-progress-error]="config().status === 'error'"
1766
+ [class.pk-progress-warning]="config().status === 'warning'"
1767
+ [attr.cx]="circleSize() / 2"
1768
+ [attr.cy]="circleSize() / 2"
1769
+ [attr.r]="radius()"
1770
+ [attr.stroke-width]="config().strokeWidth || 6"
1771
+ [attr.stroke-dasharray]="circumference()"
1772
+ [attr.stroke-dashoffset]="offset()"
1773
+ [style.stroke]="config().color || null"
1774
+ fill="none" />
1775
+ </svg>
1776
+
1777
+ @if (config().showInfo) {
1778
+ <div class="pk-progress-circle-text">
1779
+ @if (config().status === 'success') {
1780
+ <span class="pk-progress-icon">✓</span>
1781
+ } @else if (config().status === 'error') {
1782
+ <span class="pk-progress-icon">✕</span>
1783
+ } @else {
1784
+ <span>{{ config().percent }}%</span>
1785
+ }
1786
+ </div>
1787
+ }
1788
+ </div>
1789
+ } @else {
1790
+ <!-- Line Progress -->
1791
+ <div class="pk-progress-line">
1792
+ @if (config().label) {
1793
+ <div class="pk-progress-label">{{ config().label }}</div>
1794
+ }
1795
+
1796
+ <div class="pk-progress-outer">
1797
+ <div
1798
+ class="pk-progress-inner"
1799
+ [class.pk-progress-striped]="config().striped"
1800
+ [class.pk-progress-animated]="config().animated"
1801
+ [class.pk-progress-indeterminate]="config().indeterminate"
1802
+ [style.height.px]="config().strokeWidth || 8">
1803
+ <div
1804
+ class="pk-progress-bg"
1805
+ [class.pk-progress-success]="config().status === 'success'"
1806
+ [class.pk-progress-error]="config().status === 'error'"
1807
+ [class.pk-progress-warning]="config().status === 'warning'"
1808
+ [style.width.%]="config().indeterminate ? 30 : config().percent"
1809
+ [style.background]="config().color || null">
1810
+ </div>
1811
+ </div>
1812
+ </div>
1813
+
1814
+ @if (config().showInfo) {
1815
+ <div class="pk-progress-text">
1816
+ @if (config().status === 'success') {
1817
+ <span class="pk-progress-icon">✓</span>
1818
+ } @else if (config().status === 'error') {
1819
+ <span class="pk-progress-icon">✕</span>
1820
+ } @else {
1821
+ {{ config().percent }}%
1822
+ }
1823
+ </div>
1824
+ }
1825
+ </div>
1826
+ }
1827
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".pk-progress-line{display:flex;align-items:center;gap:.5rem}.pk-progress-label{flex-shrink:0;color:#565656;font-size:.875rem;font-weight:500}.pk-progress-outer{flex:1;background:#e8e8e8;border-radius:100px;overflow:hidden}.pk-progress-inner{width:100%;position:relative;background:transparent;border-radius:100px;overflow:hidden}.pk-progress-bg{height:100%;background:#0072a3;border-radius:100px;transition:width .3s ease}.pk-progress-success{background:#00a651!important}.pk-progress-error{background:#e62700!important}.pk-progress-warning{background:#ffc600!important}.pk-progress-striped .pk-progress-bg{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.pk-progress-animated .pk-progress-bg{animation:progress-stripes 1s linear infinite}@keyframes progress-stripes{0%{background-position:1rem 0}to{background-position:0 0}}.pk-progress-indeterminate .pk-progress-bg{animation:progress-indeterminate 2s linear infinite}@keyframes progress-indeterminate{0%{margin-left:0}50%{margin-left:70%}to{margin-left:0}}.pk-progress-text{flex-shrink:0;min-width:2.5rem;text-align:right;color:#565656;font-size:.875rem}.pk-progress-circle{position:relative;display:inline-flex;align-items:center;justify-content:center}.pk-progress-circle svg{transform:rotate(-90deg)}.pk-progress-circle-bg{stroke:#e8e8e8}.pk-progress-circle-path{stroke:#0072a3;stroke-linecap:round;transition:stroke-dashoffset .3s ease}.pk-progress-circle-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#565656;font-size:1rem;font-weight:600}.pk-progress-icon{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;border-radius:50%;font-size:.75rem;font-weight:700}.pk-progress-success+.pk-progress-text .pk-progress-icon,.pk-progress-circle.pk-progress-success .pk-progress-icon{color:#fff;background:#00a651}.pk-progress-error+.pk-progress-text .pk-progress-icon,.pk-progress-circle.pk-progress-error .pk-progress-icon{color:#fff;background:#e62700}.pk-progress-warning+.pk-progress-text .pk-progress-icon,.pk-progress-circle.pk-progress-warning .pk-progress-icon{color:#fff;background:#ffc600}\n"] }]
1828
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }] } });
1829
+
1830
+ class PkTreeviewNodeComponent {
1831
+ nodes = [];
1832
+ selection = 'none';
1833
+ nodeToggled = new EventEmitter();
1834
+ selectionChanged = new EventEmitter();
1835
+ toggleExpand(node, e) {
1836
+ e.stopPropagation();
1837
+ node._expanded = !node._expanded;
1838
+ this.nodeToggled.emit(node);
1839
+ }
1840
+ onRowClick(node, e) {
1841
+ if (this.selection === 'none') {
1842
+ this.toggleExpand(node, e);
1843
+ }
1844
+ else {
1845
+ this.onCheckClick(node, e);
1846
+ }
1847
+ }
1848
+ onCheckClick(node, e) {
1849
+ e.stopPropagation();
1850
+ if (this.selection === 'single') {
1851
+ this.clearAllSelection(this.nodes);
1852
+ node._selected = true;
1853
+ }
1854
+ else if (this.selection === 'multi') {
1855
+ const newState = !node._selected;
1856
+ this.setNodeAndChildren(node, newState);
1857
+ }
1858
+ this.selectionChanged.emit(this.getSelected(this.nodes));
1859
+ }
1860
+ clearAllSelection(nodes) {
1861
+ for (const n of nodes) {
1862
+ n._selected = false;
1863
+ if (n.children)
1864
+ this.clearAllSelection(n.children);
1865
+ }
1866
+ }
1867
+ setNodeAndChildren(node, selected) {
1868
+ node._selected = selected;
1869
+ node._indeterminate = false;
1870
+ if (node.children) {
1871
+ for (const c of node.children)
1872
+ this.setNodeAndChildren(c, selected);
1873
+ }
1874
+ }
1875
+ getSelected(nodes) {
1876
+ const result = [];
1877
+ for (const n of nodes) {
1878
+ if (n._selected)
1879
+ result.push(n);
1880
+ if (n.children)
1881
+ result.push(...this.getSelected(n.children));
1882
+ }
1883
+ return result;
1884
+ }
1885
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTreeviewNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1886
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkTreeviewNodeComponent, isStandalone: true, selector: "pk-treeview-node", inputs: { nodes: "nodes", selection: "selection" }, outputs: { nodeToggled: "nodeToggled", selectionChanged: "selectionChanged" }, ngImport: i0, template: `
1887
+ <ul class="pk-tree-list">
1888
+ @for (node of nodes; track node) {
1889
+ <li class="pk-tree-item">
1890
+ <!-- Row -->
1891
+ <div class="pk-tree-row"
1892
+ [class.pk-tree-row--selected]="node._selected"
1893
+ (click)="onRowClick(node, $event)">
1894
+ <!-- Expand toggle -->
1895
+ <span class="pk-tree-toggle"
1896
+ [style.visibility]="node.children?.length ? 'visible' : 'hidden'"
1897
+ (click)="toggleExpand(node, $event)">
1898
+ <pk-icon
1899
+ iconSet="material-symbols"
1900
+ [name]="node._expanded ? 'keyboard_arrow_down' : 'keyboard_arrow_right'"
1901
+ [size]="14"
1902
+ color="#64748b">
1903
+ </pk-icon>
1904
+ </span>
1905
+ <!-- Checkbox (single = radio style, multi = checkbox) -->
1906
+ @if (selection !== 'none') {
1907
+ <span class="pk-tree-check" (click)="onCheckClick(node, $event)">
1908
+ <!-- multi: tri-state checkbox -->
1909
+ @if (selection === 'multi') {
1910
+ <span class="pk-tree-cb"
1911
+ [class.pk-tree-cb--checked]="node._selected"
1912
+ [class.pk-tree-cb--indeterminate]="node._indeterminate && !node._selected">
1913
+ @if (node._selected) {
1914
+ <pk-icon iconSet="material-symbols" name="check" [size]="11" color="#fff"></pk-icon>
1915
+ }
1916
+ @if (node._indeterminate && !node._selected) {
1917
+ <span class="pk-tree-cb-dash"></span>
1918
+ }
1919
+ </span>
1920
+ }
1921
+ <!-- single: radio dot -->
1922
+ @if (selection === 'single') {
1923
+ <span class="pk-tree-radio" [class.pk-tree-radio--checked]="node._selected"></span>
1924
+ }
1925
+ </span>
1926
+ }
1927
+ <!-- Icon -->
1928
+ @if (node.icon) {
1929
+ <span class="pk-tree-icon">
1930
+ <pk-icon [name]="node.icon" [size]="16" color="#64748b"></pk-icon>
1931
+ </span>
1932
+ }
1933
+ <!-- Label -->
1934
+ <span class="pk-tree-label">
1935
+ @if (node.routerLink) {
1936
+ <a
1937
+ [routerLink]="node.routerLink"
1938
+ class="pk-tree-link"
1939
+ (click)="$event.stopPropagation()">{{ node.label }}</a>
1940
+ } @else {
1941
+ {{ node.label }}
1942
+ }
1943
+ </span>
1944
+ </div>
1945
+ <!-- Recursive children -->
1946
+ @if (node._expanded && node.children?.length) {
1947
+ <div class="pk-tree-children">
1948
+ <pk-treeview-node
1949
+ [nodes]="node.children!"
1950
+ [selection]="selection"
1951
+ (nodeToggled)="nodeToggled.emit($event)"
1952
+ (selectionChanged)="selectionChanged.emit($event)">
1953
+ </pk-treeview-node>
1954
+ </div>
1955
+ }
1956
+ </li>
1957
+ }
1958
+ </ul>
1959
+ `, isInline: true, styles: [".pk-tree-list{list-style:none;margin:0;padding:0}.pk-tree-item{margin:0}.pk-tree-row{display:flex;align-items:center;gap:2px;padding:4px 6px;border-radius:4px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s}.pk-tree-row:hover{background:#f1f5f9}.pk-tree-row--selected{background:#e0f2fe}.pk-tree-toggle{display:inline-flex;align-items:center;width:18px;cursor:pointer;flex-shrink:0}.pk-tree-check{display:inline-flex;align-items:center;flex-shrink:0;margin-right:2px}.pk-tree-icon{display:inline-flex;align-items:center;flex-shrink:0}.pk-tree-label{font-size:14px;color:#1e293b;flex:1}.pk-tree-link{color:inherit;text-decoration:none}.pk-tree-link:hover{text-decoration:underline}.pk-tree-children{padding-left:20px}.pk-tree-cb{width:15px;height:15px;border-radius:3px;border:1.5px solid #94a3b8;background:#fff;display:inline-flex;align-items:center;justify-content:center;transition:border-color .15s,background .15s}.pk-tree-cb--checked{background:#0ea5e9;border-color:#0ea5e9}.pk-tree-cb--indeterminate{border-color:#0ea5e9}.pk-tree-cb-dash{width:8px;height:2px;background:#0ea5e9;border-radius:1px;display:block}.pk-tree-radio{width:14px;height:14px;border-radius:50%;border:1.5px solid #94a3b8;background:#fff;display:inline-flex;align-items:center;justify-content:center;transition:border-color .15s}.pk-tree-radio--checked{border-color:#0ea5e9;box-shadow:inset 0 0 0 3px #0ea5e9}\n"], dependencies: [{ kind: "component", type: PkTreeviewNodeComponent, selector: "pk-treeview-node", inputs: ["nodes", "selection"], outputs: ["nodeToggled", "selectionChanged"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: PkIcon, selector: "pk-icon", inputs: ["name", "iconSet", "size", "color", "fillColor", "viewBox", "strokeWidth", "variant", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1960
+ }
1961
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTreeviewNodeComponent, decorators: [{
1962
+ type: Component,
1963
+ args: [{ selector: 'pk-treeview-node', imports: [RouterModule, PkIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1964
+ <ul class="pk-tree-list">
1965
+ @for (node of nodes; track node) {
1966
+ <li class="pk-tree-item">
1967
+ <!-- Row -->
1968
+ <div class="pk-tree-row"
1969
+ [class.pk-tree-row--selected]="node._selected"
1970
+ (click)="onRowClick(node, $event)">
1971
+ <!-- Expand toggle -->
1972
+ <span class="pk-tree-toggle"
1973
+ [style.visibility]="node.children?.length ? 'visible' : 'hidden'"
1974
+ (click)="toggleExpand(node, $event)">
1975
+ <pk-icon
1976
+ iconSet="material-symbols"
1977
+ [name]="node._expanded ? 'keyboard_arrow_down' : 'keyboard_arrow_right'"
1978
+ [size]="14"
1979
+ color="#64748b">
1980
+ </pk-icon>
1981
+ </span>
1982
+ <!-- Checkbox (single = radio style, multi = checkbox) -->
1983
+ @if (selection !== 'none') {
1984
+ <span class="pk-tree-check" (click)="onCheckClick(node, $event)">
1985
+ <!-- multi: tri-state checkbox -->
1986
+ @if (selection === 'multi') {
1987
+ <span class="pk-tree-cb"
1988
+ [class.pk-tree-cb--checked]="node._selected"
1989
+ [class.pk-tree-cb--indeterminate]="node._indeterminate && !node._selected">
1990
+ @if (node._selected) {
1991
+ <pk-icon iconSet="material-symbols" name="check" [size]="11" color="#fff"></pk-icon>
1992
+ }
1993
+ @if (node._indeterminate && !node._selected) {
1994
+ <span class="pk-tree-cb-dash"></span>
1995
+ }
1996
+ </span>
1997
+ }
1998
+ <!-- single: radio dot -->
1999
+ @if (selection === 'single') {
2000
+ <span class="pk-tree-radio" [class.pk-tree-radio--checked]="node._selected"></span>
2001
+ }
2002
+ </span>
2003
+ }
2004
+ <!-- Icon -->
2005
+ @if (node.icon) {
2006
+ <span class="pk-tree-icon">
2007
+ <pk-icon [name]="node.icon" [size]="16" color="#64748b"></pk-icon>
2008
+ </span>
2009
+ }
2010
+ <!-- Label -->
2011
+ <span class="pk-tree-label">
2012
+ @if (node.routerLink) {
2013
+ <a
2014
+ [routerLink]="node.routerLink"
2015
+ class="pk-tree-link"
2016
+ (click)="$event.stopPropagation()">{{ node.label }}</a>
2017
+ } @else {
2018
+ {{ node.label }}
2019
+ }
2020
+ </span>
2021
+ </div>
2022
+ <!-- Recursive children -->
2023
+ @if (node._expanded && node.children?.length) {
2024
+ <div class="pk-tree-children">
2025
+ <pk-treeview-node
2026
+ [nodes]="node.children!"
2027
+ [selection]="selection"
2028
+ (nodeToggled)="nodeToggled.emit($event)"
2029
+ (selectionChanged)="selectionChanged.emit($event)">
2030
+ </pk-treeview-node>
2031
+ </div>
2032
+ }
2033
+ </li>
2034
+ }
2035
+ </ul>
2036
+ `, styles: [".pk-tree-list{list-style:none;margin:0;padding:0}.pk-tree-item{margin:0}.pk-tree-row{display:flex;align-items:center;gap:2px;padding:4px 6px;border-radius:4px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s}.pk-tree-row:hover{background:#f1f5f9}.pk-tree-row--selected{background:#e0f2fe}.pk-tree-toggle{display:inline-flex;align-items:center;width:18px;cursor:pointer;flex-shrink:0}.pk-tree-check{display:inline-flex;align-items:center;flex-shrink:0;margin-right:2px}.pk-tree-icon{display:inline-flex;align-items:center;flex-shrink:0}.pk-tree-label{font-size:14px;color:#1e293b;flex:1}.pk-tree-link{color:inherit;text-decoration:none}.pk-tree-link:hover{text-decoration:underline}.pk-tree-children{padding-left:20px}.pk-tree-cb{width:15px;height:15px;border-radius:3px;border:1.5px solid #94a3b8;background:#fff;display:inline-flex;align-items:center;justify-content:center;transition:border-color .15s,background .15s}.pk-tree-cb--checked{background:#0ea5e9;border-color:#0ea5e9}.pk-tree-cb--indeterminate{border-color:#0ea5e9}.pk-tree-cb-dash{width:8px;height:2px;background:#0ea5e9;border-radius:1px;display:block}.pk-tree-radio{width:14px;height:14px;border-radius:50%;border:1.5px solid #94a3b8;background:#fff;display:inline-flex;align-items:center;justify-content:center;transition:border-color .15s}.pk-tree-radio--checked{border-color:#0ea5e9;box-shadow:inset 0 0 0 3px #0ea5e9}\n"] }]
2037
+ }], propDecorators: { nodes: [{
2038
+ type: Input
2039
+ }], selection: [{
2040
+ type: Input
2041
+ }], nodeToggled: [{
2042
+ type: Output
2043
+ }], selectionChanged: [{
2044
+ type: Output
2045
+ }] } });
2046
+
2047
+ class PkTreeviewComponent {
2048
+ cdr;
2049
+ /** Tree data */
2050
+ nodes = [];
2051
+ /** 'none' | 'single' | 'multi' */
2052
+ selection = 'none';
2053
+ /** Show "เลือกทั้งหมด" row (multi only) */
2054
+ showSelectAll = true;
2055
+ /** Initially expanded depth (0 = root collapsed) */
2056
+ expandDepth = 1;
2057
+ selected = new EventEmitter();
2058
+ nodeToggle = new EventEmitter();
2059
+ allSelected = false;
2060
+ someSelected = false;
2061
+ constructor(cdr) {
2062
+ this.cdr = cdr;
2063
+ }
2064
+ ngOnChanges(changes) {
2065
+ if (changes['nodes'] && this.nodes) {
2066
+ this.initKeys(this.nodes, '', 0);
2067
+ this.expandToDepth(this.nodes, 0);
2068
+ this.updateSelectAllState();
2069
+ }
2070
+ }
2071
+ initKeys(nodes, prefix, depth) {
2072
+ nodes.forEach((n, i) => {
2073
+ n.key = n.key ?? `${prefix}${i}`;
2074
+ n._selected = n._selected ?? false;
2075
+ n._indeterminate = false;
2076
+ n._expanded = n._expanded ?? false;
2077
+ if (n.children?.length)
2078
+ this.initKeys(n.children, `${n.key}-`, depth + 1);
2079
+ });
2080
+ }
2081
+ expandToDepth(nodes, depth) {
2082
+ for (const n of nodes) {
2083
+ n._expanded = n._expanded === true || depth < this.expandDepth;
2084
+ if (n.children?.length)
2085
+ this.expandToDepth(n.children, depth + 1);
2086
+ }
2087
+ }
2088
+ toggleSelectAll() {
2089
+ const target = !this.allSelected;
2090
+ this.setAll(this.nodes, target);
2091
+ this.updateSelectAllState();
2092
+ this.selected.emit(this.getSelected(this.nodes));
2093
+ this.cdr.markForCheck();
2094
+ }
2095
+ onNodeToggled(node) {
2096
+ this.nodeToggle.emit(node);
2097
+ this.cdr.markForCheck();
2098
+ }
2099
+ onSelectionChanged(selectedNodes) {
2100
+ if (this.selection === 'multi') {
2101
+ this.updateParentStates(this.nodes);
2102
+ this.updateSelectAllState();
2103
+ }
2104
+ this.selected.emit(selectedNodes);
2105
+ this.cdr.markForCheck();
2106
+ }
2107
+ setAll(nodes, selected) {
2108
+ for (const n of nodes) {
2109
+ n._selected = selected;
2110
+ n._indeterminate = false;
2111
+ if (n.children?.length)
2112
+ this.setAll(n.children, selected);
2113
+ }
2114
+ }
2115
+ /** Returns true=all-selected, false=none, 'indeterminate'=some */
2116
+ updateParentStates(nodes) {
2117
+ let allSel = true, noneSel = true;
2118
+ for (const n of nodes) {
2119
+ if (n.children?.length) {
2120
+ const childState = this.updateParentStates(n.children);
2121
+ if (childState === 'all') {
2122
+ n._selected = true;
2123
+ n._indeterminate = false;
2124
+ }
2125
+ else if (childState === 'none') {
2126
+ n._selected = false;
2127
+ n._indeterminate = false;
2128
+ }
2129
+ else {
2130
+ n._selected = false;
2131
+ n._indeterminate = true;
2132
+ }
2133
+ }
2134
+ if (!n._selected)
2135
+ allSel = false;
2136
+ if (n._selected || n._indeterminate)
2137
+ noneSel = false;
2138
+ }
2139
+ if (allSel)
2140
+ return 'all';
2141
+ if (noneSel)
2142
+ return 'none';
2143
+ return 'some';
2144
+ }
2145
+ updateSelectAllState() {
2146
+ const state = this.calcSelectState(this.nodes);
2147
+ this.allSelected = state === 'all';
2148
+ this.someSelected = state === 'some';
2149
+ }
2150
+ calcSelectState(nodes) {
2151
+ if (!nodes.length)
2152
+ return 'none';
2153
+ const leafs = this.getAllLeafs(nodes);
2154
+ if (!leafs.length)
2155
+ return 'none';
2156
+ const selCount = leafs.filter(n => n._selected).length;
2157
+ if (selCount === leafs.length)
2158
+ return 'all';
2159
+ if (selCount === 0)
2160
+ return 'none';
2161
+ return 'some';
2162
+ }
2163
+ getAllLeafs(nodes) {
2164
+ const result = [];
2165
+ for (const n of nodes) {
2166
+ if (!n.children?.length)
2167
+ result.push(n);
2168
+ else
2169
+ result.push(...this.getAllLeafs(n.children));
2170
+ }
2171
+ return result;
2172
+ }
2173
+ getSelected(nodes) {
2174
+ const result = [];
2175
+ for (const n of nodes) {
2176
+ if (n._selected)
2177
+ result.push(n);
2178
+ if (n.children)
2179
+ result.push(...this.getSelected(n.children));
2180
+ }
2181
+ return result;
2182
+ }
2183
+ /** Public API: get currently selected nodes */
2184
+ getSelection() {
2185
+ return this.getSelected(this.nodes);
2186
+ }
2187
+ /** Public API: clear all selection */
2188
+ clearSelection() {
2189
+ this.setAll(this.nodes, false);
2190
+ this.updateSelectAllState();
2191
+ this.cdr.markForCheck();
2192
+ }
2193
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTreeviewComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
2194
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkTreeviewComponent, isStandalone: true, selector: "pk-treeview", inputs: { nodes: "nodes", selection: "selection", showSelectAll: "showSelectAll", expandDepth: "expandDepth" }, outputs: { selected: "selected", nodeToggle: "nodeToggle" }, usesOnChanges: true, ngImport: i0, template: `
2195
+ <div class="pk-treeview">
2196
+
2197
+ <!-- Select All (multi only) -->
2198
+ @if (selection === 'multi' && showSelectAll) {
2199
+ <div class="pk-tree-select-all">
2200
+ <span class="pk-tree-cb"
2201
+ [class.pk-tree-cb--checked]="allSelected"
2202
+ [class.pk-tree-cb--indeterminate]="someSelected && !allSelected"
2203
+ (click)="toggleSelectAll()">
2204
+ @if (allSelected) {
2205
+ <pk-icon iconSet="material-symbols" name="check" [size]="11" color="#fff"></pk-icon>
2206
+ }
2207
+ @if (someSelected && !allSelected) {
2208
+ <span class="pk-tree-cb-dash"></span>
2209
+ }
2210
+ </span>
2211
+ <span class="pk-tree-select-all-label" (click)="toggleSelectAll()">เลือกทั้งหมด</span>
2212
+ </div>
2213
+ }
2214
+
2215
+ <pk-treeview-node
2216
+ [nodes]="nodes"
2217
+ [selection]="selection"
2218
+ (nodeToggled)="onNodeToggled($event)"
2219
+ (selectionChanged)="onSelectionChanged($event)">
2220
+ </pk-treeview-node>
2221
+ </div>
2222
+ `, isInline: true, styles: [".pk-treeview{font-family:inherit;font-size:14px}.pk-tree-select-all{display:flex;align-items:center;gap:6px;padding:4px 8px 6px;border-bottom:1px solid #e2e8f0;margin-bottom:4px;cursor:pointer}.pk-tree-select-all-label{font-size:13px;color:#475569;font-weight:500;-webkit-user-select:none;user-select:none}.pk-tree-cb{width:15px;height:15px;border-radius:3px;border:1.5px solid #94a3b8;background:#fff;display:inline-flex;align-items:center;justify-content:center;transition:border-color .15s,background .15s;flex-shrink:0}.pk-tree-cb--checked{background:#0ea5e9;border-color:#0ea5e9}.pk-tree-cb--indeterminate{border-color:#0ea5e9}.pk-tree-cb-dash{width:8px;height:2px;background:#0ea5e9;border-radius:1px;display:block}\n"], dependencies: [{ kind: "component", type: PkIcon, selector: "pk-icon", inputs: ["name", "iconSet", "size", "color", "fillColor", "viewBox", "strokeWidth", "variant", "fill", "weight", "grade", "opticalSize"] }, { kind: "component", type: PkTreeviewNodeComponent, selector: "pk-treeview-node", inputs: ["nodes", "selection"], outputs: ["nodeToggled", "selectionChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2223
+ }
2224
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTreeviewComponent, decorators: [{
2225
+ type: Component,
2226
+ args: [{ selector: 'pk-treeview', imports: [PkIcon, PkTreeviewNodeComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2227
+ <div class="pk-treeview">
2228
+
2229
+ <!-- Select All (multi only) -->
2230
+ @if (selection === 'multi' && showSelectAll) {
2231
+ <div class="pk-tree-select-all">
2232
+ <span class="pk-tree-cb"
2233
+ [class.pk-tree-cb--checked]="allSelected"
2234
+ [class.pk-tree-cb--indeterminate]="someSelected && !allSelected"
2235
+ (click)="toggleSelectAll()">
2236
+ @if (allSelected) {
2237
+ <pk-icon iconSet="material-symbols" name="check" [size]="11" color="#fff"></pk-icon>
2238
+ }
2239
+ @if (someSelected && !allSelected) {
2240
+ <span class="pk-tree-cb-dash"></span>
2241
+ }
2242
+ </span>
2243
+ <span class="pk-tree-select-all-label" (click)="toggleSelectAll()">เลือกทั้งหมด</span>
2244
+ </div>
2245
+ }
2246
+
2247
+ <pk-treeview-node
2248
+ [nodes]="nodes"
2249
+ [selection]="selection"
2250
+ (nodeToggled)="onNodeToggled($event)"
2251
+ (selectionChanged)="onSelectionChanged($event)">
2252
+ </pk-treeview-node>
2253
+ </div>
2254
+ `, styles: [".pk-treeview{font-family:inherit;font-size:14px}.pk-tree-select-all{display:flex;align-items:center;gap:6px;padding:4px 8px 6px;border-bottom:1px solid #e2e8f0;margin-bottom:4px;cursor:pointer}.pk-tree-select-all-label{font-size:13px;color:#475569;font-weight:500;-webkit-user-select:none;user-select:none}.pk-tree-cb{width:15px;height:15px;border-radius:3px;border:1.5px solid #94a3b8;background:#fff;display:inline-flex;align-items:center;justify-content:center;transition:border-color .15s,background .15s;flex-shrink:0}.pk-tree-cb--checked{background:#0ea5e9;border-color:#0ea5e9}.pk-tree-cb--indeterminate{border-color:#0ea5e9}.pk-tree-cb-dash{width:8px;height:2px;background:#0ea5e9;border-radius:1px;display:block}\n"] }]
2255
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { nodes: [{
2256
+ type: Input
2257
+ }], selection: [{
2258
+ type: Input
2259
+ }], showSelectAll: [{
2260
+ type: Input
2261
+ }], expandDepth: [{
2262
+ type: Input
2263
+ }], selected: [{
2264
+ type: Output
2265
+ }], nodeToggle: [{
2266
+ type: Output
2267
+ }] } });
2268
+
2269
+ class PkTreeviewModule {
2270
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTreeviewModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2271
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: PkTreeviewModule, imports: [PkTreeviewComponent, PkTreeviewNodeComponent], exports: [PkTreeviewComponent, PkTreeviewNodeComponent] });
2272
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTreeviewModule, imports: [PkTreeviewComponent, PkTreeviewNodeComponent] });
2273
+ }
2274
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTreeviewModule, decorators: [{
2275
+ type: NgModule,
2276
+ args: [{
2277
+ imports: [PkTreeviewComponent, PkTreeviewNodeComponent],
2278
+ exports: [PkTreeviewComponent, PkTreeviewNodeComponent]
2279
+ }]
2280
+ }] });
2281
+
2282
+ class PkSelectComponent {
2283
+ elementRef = inject(ElementRef);
2284
+ options = input.required(...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
2285
+ mode = input('single', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
2286
+ placeholder = input('เลือก...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
2287
+ searchable = input(false, ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
2288
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
2289
+ labelField = input('label', ...(ngDevMode ? [{ debugName: "labelField" }] : /* istanbul ignore next */ []));
2290
+ valueField = input('value', ...(ngDevMode ? [{ debugName: "valueField" }] : /* istanbul ignore next */ []));
2291
+ returnObjects = input(false, ...(ngDevMode ? [{ debugName: "returnObjects" }] : /* istanbul ignore next */ []));
2292
+ change = output();
2293
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
2294
+ searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
2295
+ selectedValues = signal([], ...(ngDevMode ? [{ debugName: "selectedValues" }] : /* istanbul ignore next */ []));
2296
+ onChange = () => { };
2297
+ onTouched = () => { };
2298
+ filteredOptions = computed(() => {
2299
+ const query = this.searchQuery().toLowerCase();
2300
+ if (!query)
2301
+ return this.options();
2302
+ return this.options().filter(option => this.getOptionLabel(option).toLowerCase().includes(query));
2303
+ }, ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
2304
+ displayValue = computed(() => {
2305
+ const selected = this.selectedValues();
2306
+ const opts = this.options();
2307
+ if (selected.length === 0)
2308
+ return '';
2309
+ if (this.mode() === 'single') {
2310
+ const option = opts.find(o => this.getOptionValue(o) === selected[0]);
2311
+ return option ? this.getOptionLabel(option) : '';
2312
+ }
2313
+ else {
2314
+ const labels = selected
2315
+ .map(val => {
2316
+ const option = opts.find(o => this.getOptionValue(o) === val);
2317
+ return option ? this.getOptionLabel(option) : '';
2318
+ })
2319
+ .filter(Boolean);
2320
+ return labels.join(', ');
2321
+ }
2322
+ }, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
2323
+ toggleDropdown() {
2324
+ if (this.disabled())
2325
+ return;
2326
+ this.isOpen.update(open => !open);
2327
+ if (!this.isOpen()) {
2328
+ this.searchQuery.set('');
2329
+ }
2330
+ }
2331
+ selectOption(option) {
2332
+ if (option.disabled)
2333
+ return;
2334
+ const optionValue = this.getOptionValue(option);
2335
+ if (this.mode() === 'single') {
2336
+ this.selectedValues.set([optionValue]);
2337
+ this.isOpen.set(false);
2338
+ this.searchQuery.set('');
2339
+ this.emitValue();
2340
+ }
2341
+ else {
2342
+ const current = [...this.selectedValues()];
2343
+ const index = current.indexOf(optionValue);
2344
+ if (index > -1) {
2345
+ current.splice(index, 1);
2346
+ }
2347
+ else {
2348
+ current.push(optionValue);
2349
+ }
2350
+ this.selectedValues.set(current);
2351
+ this.emitValue();
2352
+ }
2353
+ }
2354
+ isSelected(value) {
2355
+ return this.selectedValues().includes(value);
2356
+ }
2357
+ onSearchInput(event) {
2358
+ const target = event.target;
2359
+ this.searchQuery.set(target.value);
2360
+ }
2361
+ onDocumentClick(event) {
2362
+ // Close dropdown when clicking outside
2363
+ const clickedInside = this.elementRef.nativeElement.contains(event.target);
2364
+ if (!clickedInside && this.isOpen()) {
2365
+ this.isOpen.set(false);
2366
+ this.searchQuery.set('');
2367
+ }
2368
+ }
2369
+ onCheckboxClick(option, event) {
2370
+ event.preventDefault();
2371
+ event.stopPropagation();
2372
+ if (option.disabled) {
2373
+ return;
2374
+ }
2375
+ this.selectOption(option);
2376
+ }
2377
+ emitValue() {
2378
+ const selected = this.selectedValues();
2379
+ let value;
2380
+ if (this.returnObjects()) {
2381
+ const selectedOptions = selected
2382
+ .map(selectedValue => this.options().find(option => this.getOptionValue(option) === selectedValue))
2383
+ .filter(Boolean);
2384
+ value = this.mode() === 'single'
2385
+ ? selectedOptions[0] ?? null
2386
+ : selectedOptions;
2387
+ }
2388
+ else {
2389
+ value = this.mode() === 'single'
2390
+ ? selected[0] ?? null
2391
+ : selected;
2392
+ }
2393
+ this.onChange(value);
2394
+ this.onTouched();
2395
+ this.change.emit(value);
2396
+ }
2397
+ writeValue(value) {
2398
+ if (value === null || value === undefined) {
2399
+ this.selectedValues.set([]);
2400
+ }
2401
+ else if (Array.isArray(value)) {
2402
+ this.selectedValues.set(value.map(item => this.extractIncomingValue(item)));
2403
+ }
2404
+ else {
2405
+ this.selectedValues.set([this.extractIncomingValue(value)]);
2406
+ }
2407
+ }
2408
+ registerOnChange(fn) {
2409
+ this.onChange = fn;
2410
+ }
2411
+ registerOnTouched(fn) {
2412
+ this.onTouched = fn;
2413
+ }
2414
+ setDisabledState(isDisabled) {
2415
+ // Handled by disabled input
2416
+ }
2417
+ getOptionLabel(option) {
2418
+ const field = this.labelField();
2419
+ const fallbackField = this.valueField();
2420
+ const value = option?.[field] ?? option?.label ?? option?.[fallbackField] ?? option?.value ?? '';
2421
+ return `${value}`;
2422
+ }
2423
+ getOptionValue(option) {
2424
+ const field = this.valueField();
2425
+ const fallbackField = this.labelField();
2426
+ return option?.[field] ?? option?.value ?? option?.[fallbackField] ?? option?.label ?? '';
2427
+ }
2428
+ extractIncomingValue(value) {
2429
+ if (value !== null && typeof value === 'object') {
2430
+ return value?.[this.valueField()] ?? value?.value ?? value?.[this.labelField()] ?? value?.label ?? '';
2431
+ }
2432
+ return value;
2433
+ }
2434
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2435
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkSelectComponent, isStandalone: true, selector: "pk-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, labelField: { classPropertyName: "labelField", publicName: "labelField", isSignal: true, isRequired: false, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, returnObjects: { classPropertyName: "returnObjects", publicName: "returnObjects", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, providers: [
2436
+ {
2437
+ provide: NG_VALUE_ACCESSOR,
2438
+ useExisting: forwardRef(() => PkSelectComponent),
2439
+ multi: true
2440
+ }
2441
+ ], ngImport: i0, template: `
2442
+ <div class="pk-select-container">
2443
+ <div
2444
+ class="pk-select-trigger"
2445
+ [class.pk-select-trigger-open]="isOpen()"
2446
+ [class.pk-select-trigger-disabled]="disabled()"
2447
+ (click)="toggleDropdown()">
2448
+ <span class="pk-select-value">
2449
+ @if (displayValue()) {
2450
+ {{ displayValue() }}
2451
+ } @else {
2452
+ <span class="pk-select-placeholder">{{ placeholder() }}</span>
2453
+ }
2454
+ </span>
2455
+ <span class="pk-select-arrow" [class.pk-select-arrow-open]="isOpen()">▼</span>
2456
+ </div>
2457
+
2458
+ @if (isOpen()) {
2459
+ <div class="pk-select-dropdown">
2460
+ @if (searchable()) {
2461
+ <div class="pk-select-search">
2462
+ <input
2463
+ type="text"
2464
+ class="pk-select-search-input"
2465
+ [value]="searchQuery()"
2466
+ (input)="onSearchInput($event)"
2467
+ (click)="$event.stopPropagation()"
2468
+ placeholder="ค้นหา..." />
2469
+ </div>
2470
+ }
2471
+
2472
+ <div class="pk-select-options">
2473
+ @if (filteredOptions().length === 0) {
2474
+ <div class="pk-select-no-options">ไม่พบข้อมูล</div>
2475
+ }
2476
+
2477
+ @for (option of filteredOptions(); track getOptionValue(option)) {
2478
+ <div
2479
+ class="pk-select-option"
2480
+ [class.pk-select-option-selected]="isSelected(getOptionValue(option))"
2481
+ [class.pk-select-option-disabled]="option.disabled"
2482
+ (click)="!option.disabled && selectOption(option)">
2483
+
2484
+ @if (mode() === 'multi') {
2485
+ <input
2486
+ type="checkbox"
2487
+ class="pk-select-checkbox"
2488
+ [checked]="isSelected(getOptionValue(option))"
2489
+ [disabled]="option.disabled"
2490
+ (click)="onCheckboxClick(option, $event)"
2491
+ (change)="$event.stopPropagation()" />
2492
+ }
2493
+
2494
+ <span class="pk-select-option-label">{{ option.label }}</span>
2495
+ </div>
2496
+ }
2497
+ </div>
2498
+ </div>
2499
+ }
2500
+ </div>
2501
+ `, isInline: true, styles: [".pk-select-container{position:relative;width:100%}.pk-select-trigger{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;background-color:#fff;cursor:pointer;transition:all .2s;min-height:38px}.pk-select-trigger:hover:not(.pk-select-trigger-disabled){border-color:#3b82f6}.pk-select-trigger-open{border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.pk-select-trigger-disabled{background-color:#f3f4f6;cursor:not-allowed;opacity:.6}.pk-select-value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pk-select-placeholder{color:#9ca3af}.pk-select-arrow{margin-left:8px;font-size:12px;color:#6b7280;transition:transform .2s}.pk-select-arrow-open{transform:rotate(180deg)}.pk-select-dropdown{position:absolute;top:calc(100% + 4px);left:0;right:0;background-color:#fff;border:1px solid #d1d5db;border-radius:6px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;z-index:1000;max-height:300px;overflow:hidden;display:flex;flex-direction:column}.pk-select-search{padding:8px;border-bottom:1px solid #e5e7eb}.pk-select-search-input{width:100%;padding:6px 10px;border:1px solid #d1d5db;border-radius:4px;font-size:14px;outline:none}.pk-select-search-input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.pk-select-options{overflow-y:auto;max-height:250px}.pk-select-option{display:flex;align-items:center;padding:8px 12px;cursor:pointer;transition:background-color .2s}.pk-select-option:hover:not(.pk-select-option-disabled){background-color:#f3f4f6}.pk-select-option-selected{background-color:#dbeafe;color:#1e40af}.pk-select-option-disabled{opacity:.5;cursor:not-allowed}.pk-select-checkbox{margin-right:8px;cursor:pointer}.pk-select-option-label{flex:1}.pk-select-no-options{padding:16px 12px;text-align:center;color:#9ca3af;font-size:14px}\n"] });
2502
+ }
2503
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkSelectComponent, decorators: [{
2504
+ type: Component,
2505
+ args: [{ selector: 'pk-select', standalone: true, imports: [], providers: [
2506
+ {
2507
+ provide: NG_VALUE_ACCESSOR,
2508
+ useExisting: forwardRef(() => PkSelectComponent),
2509
+ multi: true
2510
+ }
2511
+ ], host: {
2512
+ '(document:click)': 'onDocumentClick($event)'
2513
+ }, template: `
2514
+ <div class="pk-select-container">
2515
+ <div
2516
+ class="pk-select-trigger"
2517
+ [class.pk-select-trigger-open]="isOpen()"
2518
+ [class.pk-select-trigger-disabled]="disabled()"
2519
+ (click)="toggleDropdown()">
2520
+ <span class="pk-select-value">
2521
+ @if (displayValue()) {
2522
+ {{ displayValue() }}
2523
+ } @else {
2524
+ <span class="pk-select-placeholder">{{ placeholder() }}</span>
2525
+ }
2526
+ </span>
2527
+ <span class="pk-select-arrow" [class.pk-select-arrow-open]="isOpen()">▼</span>
2528
+ </div>
2529
+
2530
+ @if (isOpen()) {
2531
+ <div class="pk-select-dropdown">
2532
+ @if (searchable()) {
2533
+ <div class="pk-select-search">
2534
+ <input
2535
+ type="text"
2536
+ class="pk-select-search-input"
2537
+ [value]="searchQuery()"
2538
+ (input)="onSearchInput($event)"
2539
+ (click)="$event.stopPropagation()"
2540
+ placeholder="ค้นหา..." />
2541
+ </div>
2542
+ }
2543
+
2544
+ <div class="pk-select-options">
2545
+ @if (filteredOptions().length === 0) {
2546
+ <div class="pk-select-no-options">ไม่พบข้อมูล</div>
2547
+ }
2548
+
2549
+ @for (option of filteredOptions(); track getOptionValue(option)) {
2550
+ <div
2551
+ class="pk-select-option"
2552
+ [class.pk-select-option-selected]="isSelected(getOptionValue(option))"
2553
+ [class.pk-select-option-disabled]="option.disabled"
2554
+ (click)="!option.disabled && selectOption(option)">
2555
+
2556
+ @if (mode() === 'multi') {
2557
+ <input
2558
+ type="checkbox"
2559
+ class="pk-select-checkbox"
2560
+ [checked]="isSelected(getOptionValue(option))"
2561
+ [disabled]="option.disabled"
2562
+ (click)="onCheckboxClick(option, $event)"
2563
+ (change)="$event.stopPropagation()" />
2564
+ }
2565
+
2566
+ <span class="pk-select-option-label">{{ option.label }}</span>
2567
+ </div>
2568
+ }
2569
+ </div>
2570
+ </div>
2571
+ }
2572
+ </div>
2573
+ `, styles: [".pk-select-container{position:relative;width:100%}.pk-select-trigger{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;background-color:#fff;cursor:pointer;transition:all .2s;min-height:38px}.pk-select-trigger:hover:not(.pk-select-trigger-disabled){border-color:#3b82f6}.pk-select-trigger-open{border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.pk-select-trigger-disabled{background-color:#f3f4f6;cursor:not-allowed;opacity:.6}.pk-select-value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pk-select-placeholder{color:#9ca3af}.pk-select-arrow{margin-left:8px;font-size:12px;color:#6b7280;transition:transform .2s}.pk-select-arrow-open{transform:rotate(180deg)}.pk-select-dropdown{position:absolute;top:calc(100% + 4px);left:0;right:0;background-color:#fff;border:1px solid #d1d5db;border-radius:6px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;z-index:1000;max-height:300px;overflow:hidden;display:flex;flex-direction:column}.pk-select-search{padding:8px;border-bottom:1px solid #e5e7eb}.pk-select-search-input{width:100%;padding:6px 10px;border:1px solid #d1d5db;border-radius:4px;font-size:14px;outline:none}.pk-select-search-input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.pk-select-options{overflow-y:auto;max-height:250px}.pk-select-option{display:flex;align-items:center;padding:8px 12px;cursor:pointer;transition:background-color .2s}.pk-select-option:hover:not(.pk-select-option-disabled){background-color:#f3f4f6}.pk-select-option-selected{background-color:#dbeafe;color:#1e40af}.pk-select-option-disabled{opacity:.5;cursor:not-allowed}.pk-select-checkbox{margin-right:8px;cursor:pointer}.pk-select-option-label{flex:1}.pk-select-no-options{padding:16px 12px;text-align:center;color:#9ca3af;font-size:14px}\n"] }]
2574
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], labelField: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelField", required: false }] }], valueField: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueField", required: false }] }], returnObjects: [{ type: i0.Input, args: [{ isSignal: true, alias: "returnObjects", required: false }] }], change: [{ type: i0.Output, args: ["change"] }] } });
2575
+
2576
+ class PkAutocompleteComponent {
2577
+ elementRef = inject(ElementRef);
2578
+ // Inputs
2579
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
2580
+ fetchFn = input(null, ...(ngDevMode ? [{ debugName: "fetchFn" }] : /* istanbul ignore next */ []));
2581
+ placeholder = input('ค้นหา...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
2582
+ minChars = input(1, ...(ngDevMode ? [{ debugName: "minChars" }] : /* istanbul ignore next */ []));
2583
+ debounceTime = input(300, ...(ngDevMode ? [{ debugName: "debounceTime" }] : /* istanbul ignore next */ []));
2584
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
2585
+ displayKey = input('label', ...(ngDevMode ? [{ debugName: "displayKey" }] : /* istanbul ignore next */ []));
2586
+ // State
2587
+ searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
2588
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
2589
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
2590
+ highlightedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "highlightedIndex" }] : /* istanbul ignore next */ []));
2591
+ selectedValue = signal(null, ...(ngDevMode ? [{ debugName: "selectedValue" }] : /* istanbul ignore next */ []));
2592
+ fetchedOptions = signal([], ...(ngDevMode ? [{ debugName: "fetchedOptions" }] : /* istanbul ignore next */ []));
2593
+ debounceTimer = null;
2594
+ onChange = () => { };
2595
+ onTouched = () => { };
2596
+ constructor() {
2597
+ // Auto-fetch when search term changes
2598
+ effect(() => {
2599
+ const term = this.searchTerm();
2600
+ const fn = this.fetchFn();
2601
+ const min = this.minChars();
2602
+ if (fn && term.length >= min) {
2603
+ this.performSearch(term);
2604
+ }
2605
+ });
2606
+ }
2607
+ // Computed
2608
+ filteredOptions = computed(() => {
2609
+ const fetchFn = this.fetchFn();
2610
+ const term = this.searchTerm().toLowerCase().trim();
2611
+ // If using fetchFn, return fetched options
2612
+ if (fetchFn) {
2613
+ return this.fetchedOptions();
2614
+ }
2615
+ // Otherwise filter local options
2616
+ const opts = this.options();
2617
+ if (!term)
2618
+ return opts;
2619
+ return opts.filter(opt => opt.label.toLowerCase().includes(term));
2620
+ }, ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
2621
+ displayValue = computed(() => {
2622
+ const selected = this.selectedValue();
2623
+ if (!selected)
2624
+ return this.searchTerm();
2625
+ const opts = this.fetchFn() ? this.fetchedOptions() : this.options();
2626
+ const option = opts.find(opt => opt.value === selected);
2627
+ return option ? option.label : this.searchTerm();
2628
+ }, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
2629
+ onClickOutside(event) {
2630
+ if (!this.elementRef.nativeElement.contains(event.target)) {
2631
+ this.close();
2632
+ }
2633
+ }
2634
+ onInput(event) {
2635
+ const input = event.target;
2636
+ const value = input.value;
2637
+ this.searchTerm.set(value);
2638
+ this.isOpen.set(true);
2639
+ this.highlightedIndex.set(-1);
2640
+ // Clear selection if input is cleared
2641
+ if (!value) {
2642
+ this.selectedValue.set(null);
2643
+ this.onChange(null);
2644
+ }
2645
+ // Debounce search
2646
+ if (this.fetchFn()) {
2647
+ if (this.debounceTimer) {
2648
+ clearTimeout(this.debounceTimer);
2649
+ }
2650
+ if (value.length >= this.minChars()) {
2651
+ this.debounceTimer = setTimeout(() => {
2652
+ this.performSearch(value);
2653
+ }, this.debounceTime());
2654
+ }
2655
+ }
2656
+ }
2657
+ onFocus() {
2658
+ if (!this.disabled()) {
2659
+ this.isOpen.set(true);
2660
+ // If no search term, fetch or show all options
2661
+ if (!this.searchTerm() && this.fetchFn()) {
2662
+ this.performSearch('');
2663
+ }
2664
+ }
2665
+ }
2666
+ onKeyDown(event) {
2667
+ const opts = this.filteredOptions();
2668
+ switch (event.key) {
2669
+ case 'ArrowDown':
2670
+ event.preventDefault();
2671
+ if (!this.isOpen()) {
2672
+ this.isOpen.set(true);
2673
+ }
2674
+ else {
2675
+ const newIndex = Math.min(this.highlightedIndex() + 1, opts.length - 1);
2676
+ this.highlightedIndex.set(newIndex);
2677
+ }
2678
+ break;
2679
+ case 'ArrowUp':
2680
+ event.preventDefault();
2681
+ const newIndex = Math.max(this.highlightedIndex() - 1, 0);
2682
+ this.highlightedIndex.set(newIndex);
2683
+ break;
2684
+ case 'Enter':
2685
+ event.preventDefault();
2686
+ const idx = this.highlightedIndex();
2687
+ if (idx >= 0 && idx < opts.length) {
2688
+ this.selectOption(opts[idx]);
2689
+ }
2690
+ break;
2691
+ case 'Escape':
2692
+ event.preventDefault();
2693
+ this.close();
2694
+ break;
2695
+ }
2696
+ }
2697
+ async performSearch(term) {
2698
+ const fn = this.fetchFn();
2699
+ if (!fn)
2700
+ return;
2701
+ this.loading.set(true);
2702
+ try {
2703
+ const results = await fn(term);
2704
+ this.fetchedOptions.set(results);
2705
+ }
2706
+ catch (error) {
2707
+ console.error('Autocomplete fetch error:', error);
2708
+ this.fetchedOptions.set([]);
2709
+ }
2710
+ finally {
2711
+ this.loading.set(false);
2712
+ }
2713
+ }
2714
+ selectOption(option) {
2715
+ if (option.disabled)
2716
+ return;
2717
+ this.selectedValue.set(option.value);
2718
+ this.searchTerm.set(option.label);
2719
+ this.onChange(option.value);
2720
+ this.onTouched();
2721
+ this.close();
2722
+ }
2723
+ clear() {
2724
+ this.searchTerm.set('');
2725
+ this.selectedValue.set(null);
2726
+ this.onChange(null);
2727
+ this.isOpen.set(false);
2728
+ }
2729
+ close() {
2730
+ this.isOpen.set(false);
2731
+ this.highlightedIndex.set(-1);
2732
+ }
2733
+ isSelected(option) {
2734
+ return this.selectedValue() === option.value;
2735
+ }
2736
+ // ControlValueAccessor implementation
2737
+ writeValue(value) {
2738
+ this.selectedValue.set(value);
2739
+ // Find and set display text
2740
+ if (value) {
2741
+ const opts = this.fetchFn() ? this.fetchedOptions() : this.options();
2742
+ const option = opts.find(opt => opt.value === value);
2743
+ if (option) {
2744
+ this.searchTerm.set(option.label);
2745
+ }
2746
+ }
2747
+ else {
2748
+ this.searchTerm.set('');
2749
+ }
2750
+ }
2751
+ registerOnChange(fn) {
2752
+ this.onChange = fn;
2753
+ }
2754
+ registerOnTouched(fn) {
2755
+ this.onTouched = fn;
2756
+ }
2757
+ setDisabledState(isDisabled) {
2758
+ // Handled by input() signal
2759
+ }
2760
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkAutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2761
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkAutocompleteComponent, isStandalone: true, selector: "pk-autocomplete", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, fetchFn: { classPropertyName: "fetchFn", publicName: "fetchFn", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, minChars: { classPropertyName: "minChars", publicName: "minChars", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, displayKey: { classPropertyName: "displayKey", publicName: "displayKey", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onClickOutside($event)" } }, providers: [
2762
+ {
2763
+ provide: NG_VALUE_ACCESSOR,
2764
+ useExisting: forwardRef(() => PkAutocompleteComponent),
2765
+ multi: true,
2766
+ },
2767
+ ], ngImport: i0, template: `
2768
+ <div class="relative w-full">
2769
+ <!-- Input -->
2770
+ <div class="relative">
2771
+ <input
2772
+ #inputElement
2773
+ type="text"
2774
+ [value]="displayValue()"
2775
+ (input)="onInput($event)"
2776
+ (focus)="onFocus()"
2777
+ (keydown)="onKeyDown($event)"
2778
+ [placeholder]="placeholder()"
2779
+ [disabled]="disabled()"
2780
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
2781
+ [class.pr-10]="loading() || (searchTerm() && !loading())"
2782
+ />
2783
+
2784
+ <!-- Loading Spinner or Clear Button -->
2785
+ @if (loading()) {
2786
+ <div class="absolute right-3 top-1/2 -translate-y-1/2">
2787
+ <div class="pk-spinner pk-spinner-sm"></div>
2788
+ </div>
2789
+ } @else if (searchTerm() && !disabled()) {
2790
+ <button
2791
+ type="button"
2792
+ (click)="clear()"
2793
+ class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600">
2794
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2795
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
2796
+ </svg>
2797
+ </button>
2798
+ }
2799
+ </div>
2800
+
2801
+ <!-- Dropdown -->
2802
+ @if (isOpen() && !disabled()) {
2803
+ <div class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-auto">
2804
+ @if (loading()) {
2805
+ <div class="px-4 py-8 text-center text-gray-500">
2806
+ <div class="pk-spinner pk-spinner-sm mx-auto mb-2"></div>
2807
+ <p class="text-sm">กำลังค้นหา...</p>
2808
+ </div>
2809
+ } @else if (filteredOptions().length === 0) {
2810
+ <div class="px-4 py-8 text-center text-gray-500">
2811
+ <svg class="w-12 h-12 mx-auto mb-2 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2812
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
2813
+ </svg>
2814
+ <p class="text-sm">ไม่พบข้อมูล</p>
2815
+ @if (searchTerm()) {
2816
+ <p class="text-xs mt-1">ลองค้นหาด้วยคำอื่น</p>
2817
+ }
2818
+ </div>
2819
+ } @else {
2820
+ @for (option of filteredOptions(); track option.value; let idx = $index) {
2821
+ <button
2822
+ type="button"
2823
+ (click)="selectOption(option)"
2824
+ [disabled]="option.disabled"
2825
+ class="w-full px-4 py-2 text-left hover:bg-blue-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
2826
+ [class.bg-blue-100]="highlightedIndex() === idx"
2827
+ [class.font-medium]="isSelected(option)">
2828
+ {{ option.label }}
2829
+ @if (isSelected(option)) {
2830
+ <span class="float-right text-blue-600">✓</span>
2831
+ }
2832
+ </button>
2833
+ }
2834
+ }
2835
+ </div>
2836
+ }
2837
+ </div>
2838
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2839
+ }
2840
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkAutocompleteComponent, decorators: [{
2841
+ type: Component,
2842
+ args: [{
2843
+ selector: 'pk-autocomplete',
2844
+ standalone: true,
2845
+ imports: [FormsModule],
2846
+ providers: [
2847
+ {
2848
+ provide: NG_VALUE_ACCESSOR,
2849
+ useExisting: forwardRef(() => PkAutocompleteComponent),
2850
+ multi: true,
2851
+ },
2852
+ ],
2853
+ template: `
2854
+ <div class="relative w-full">
2855
+ <!-- Input -->
2856
+ <div class="relative">
2857
+ <input
2858
+ #inputElement
2859
+ type="text"
2860
+ [value]="displayValue()"
2861
+ (input)="onInput($event)"
2862
+ (focus)="onFocus()"
2863
+ (keydown)="onKeyDown($event)"
2864
+ [placeholder]="placeholder()"
2865
+ [disabled]="disabled()"
2866
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
2867
+ [class.pr-10]="loading() || (searchTerm() && !loading())"
2868
+ />
2869
+
2870
+ <!-- Loading Spinner or Clear Button -->
2871
+ @if (loading()) {
2872
+ <div class="absolute right-3 top-1/2 -translate-y-1/2">
2873
+ <div class="pk-spinner pk-spinner-sm"></div>
2874
+ </div>
2875
+ } @else if (searchTerm() && !disabled()) {
2876
+ <button
2877
+ type="button"
2878
+ (click)="clear()"
2879
+ class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600">
2880
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2881
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
2882
+ </svg>
2883
+ </button>
2884
+ }
2885
+ </div>
2886
+
2887
+ <!-- Dropdown -->
2888
+ @if (isOpen() && !disabled()) {
2889
+ <div class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-auto">
2890
+ @if (loading()) {
2891
+ <div class="px-4 py-8 text-center text-gray-500">
2892
+ <div class="pk-spinner pk-spinner-sm mx-auto mb-2"></div>
2893
+ <p class="text-sm">กำลังค้นหา...</p>
2894
+ </div>
2895
+ } @else if (filteredOptions().length === 0) {
2896
+ <div class="px-4 py-8 text-center text-gray-500">
2897
+ <svg class="w-12 h-12 mx-auto mb-2 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2898
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
2899
+ </svg>
2900
+ <p class="text-sm">ไม่พบข้อมูล</p>
2901
+ @if (searchTerm()) {
2902
+ <p class="text-xs mt-1">ลองค้นหาด้วยคำอื่น</p>
2903
+ }
2904
+ </div>
2905
+ } @else {
2906
+ @for (option of filteredOptions(); track option.value; let idx = $index) {
2907
+ <button
2908
+ type="button"
2909
+ (click)="selectOption(option)"
2910
+ [disabled]="option.disabled"
2911
+ class="w-full px-4 py-2 text-left hover:bg-blue-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
2912
+ [class.bg-blue-100]="highlightedIndex() === idx"
2913
+ [class.font-medium]="isSelected(option)">
2914
+ {{ option.label }}
2915
+ @if (isSelected(option)) {
2916
+ <span class="float-right text-blue-600">✓</span>
2917
+ }
2918
+ </button>
2919
+ }
2920
+ }
2921
+ </div>
2922
+ }
2923
+ </div>
2924
+ `,
2925
+ changeDetection: ChangeDetectionStrategy.OnPush,
2926
+ }]
2927
+ }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], fetchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "fetchFn", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], minChars: [{ type: i0.Input, args: [{ isSignal: true, alias: "minChars", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], displayKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayKey", required: false }] }], onClickOutside: [{
2928
+ type: HostListener,
2929
+ args: ['document:click', ['$event']]
2930
+ }] } });
2931
+
2932
+ class PkTypeaheadComponent {
2933
+ elementRef;
2934
+ items = [];
2935
+ placeholder = '';
2936
+ disabled = false;
2937
+ style = '';
2938
+ customClass = '';
2939
+ customStyle = { width: '100%' };
2940
+ labelField = 'label';
2941
+ valueField = 'value';
2942
+ allowCustomValue = true;
2943
+ maxItems = 12;
2944
+ minChars = 0;
2945
+ itemSelected = new EventEmitter();
2946
+ value = '';
2947
+ isOpen = false;
2948
+ highlightedIndex = -1;
2949
+ onChange = () => { };
2950
+ onTouch = () => { };
2951
+ constructor(elementRef) {
2952
+ this.elementRef = elementRef;
2953
+ }
2954
+ get filteredItems() {
2955
+ const keyword = (this.value || '').trim().toLowerCase();
2956
+ const normalizedItems = this.items || [];
2957
+ if (keyword.length < this.minChars) {
2958
+ return normalizedItems.slice(0, this.maxItems);
2959
+ }
2960
+ return normalizedItems
2961
+ .filter(item => this.getItemLabel(item).toLowerCase().includes(keyword))
2962
+ .slice(0, this.maxItems);
2963
+ }
2964
+ writeValue(value) {
2965
+ this.value = value || '';
2966
+ }
2967
+ registerOnChange(fn) {
2968
+ this.onChange = fn;
2969
+ }
2970
+ registerOnTouched(fn) {
2971
+ this.onTouch = fn;
2972
+ }
2973
+ setDisabledState(isDisabled) {
2974
+ this.disabled = isDisabled;
2975
+ }
2976
+ onInput(event) {
2977
+ const target = event.target;
2978
+ this.value = target.value;
2979
+ this.onChange(this.value);
2980
+ this.isOpen = true;
2981
+ this.highlightedIndex = this.filteredItems.length ? 0 : -1;
2982
+ }
2983
+ onFocus() {
2984
+ if (this.disabled) {
2985
+ return;
2986
+ }
2987
+ this.isOpen = true;
2988
+ this.highlightedIndex = this.filteredItems.length ? 0 : -1;
2989
+ }
2990
+ onBlur() {
2991
+ this.onTouch();
2992
+ if (!this.allowCustomValue) {
2993
+ const exactMatch = (this.items || []).find(item => this.getItemLabel(item) === this.value);
2994
+ if (!exactMatch) {
2995
+ this.value = '';
2996
+ this.onChange(this.value);
2997
+ }
2998
+ }
2999
+ }
3000
+ onKeydown(event) {
3001
+ if (!this.isOpen && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
3002
+ this.isOpen = true;
3003
+ }
3004
+ if (!this.filteredItems.length) {
3005
+ if (event.key === 'Escape') {
3006
+ this.isOpen = false;
3007
+ }
3008
+ return;
3009
+ }
3010
+ if (event.key === 'ArrowDown') {
3011
+ event.preventDefault();
3012
+ this.highlightedIndex = (this.highlightedIndex + 1) % this.filteredItems.length;
3013
+ }
3014
+ if (event.key === 'ArrowUp') {
3015
+ event.preventDefault();
3016
+ this.highlightedIndex = this.highlightedIndex <= 0 ? this.filteredItems.length - 1 : this.highlightedIndex - 1;
3017
+ }
3018
+ if (event.key === 'Enter' && this.highlightedIndex >= 0) {
3019
+ event.preventDefault();
3020
+ this.selectItem(this.filteredItems[this.highlightedIndex]);
3021
+ }
3022
+ if (event.key === 'Escape') {
3023
+ this.isOpen = false;
3024
+ }
3025
+ }
3026
+ selectItem(item) {
3027
+ this.value = this.getItemValue(item);
3028
+ this.onChange(this.value);
3029
+ this.itemSelected.emit(item);
3030
+ this.isOpen = false;
3031
+ this.highlightedIndex = -1;
3032
+ }
3033
+ getItemLabel(item) {
3034
+ if (item == null) {
3035
+ return '';
3036
+ }
3037
+ if (typeof item === 'string' || typeof item === 'number') {
3038
+ return `${item}`;
3039
+ }
3040
+ return `${item[this.labelField] ?? item[this.valueField] ?? ''}`;
3041
+ }
3042
+ getItemValue(item) {
3043
+ if (item == null) {
3044
+ return '';
3045
+ }
3046
+ if (typeof item === 'string' || typeof item === 'number') {
3047
+ return `${item}`;
3048
+ }
3049
+ return `${item[this.valueField] ?? item[this.labelField] ?? ''}`;
3050
+ }
3051
+ trackByItem(_, item) {
3052
+ return this.getItemValue(item);
3053
+ }
3054
+ onDocumentClick(event) {
3055
+ if (!this.elementRef.nativeElement.contains(event.target)) {
3056
+ this.isOpen = false;
3057
+ this.highlightedIndex = -1;
3058
+ }
3059
+ }
3060
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTypeaheadComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
3061
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PkTypeaheadComponent, isStandalone: true, selector: "pk-typeahead", inputs: { items: "items", placeholder: "placeholder", disabled: "disabled", style: "style", customClass: "customClass", customStyle: "customStyle", labelField: "labelField", valueField: "valueField", allowCustomValue: "allowCustomValue", maxItems: "maxItems", minChars: "minChars" }, outputs: { itemSelected: "itemSelected" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, providers: [
3062
+ {
3063
+ provide: NG_VALUE_ACCESSOR,
3064
+ useExisting: forwardRef(() => PkTypeaheadComponent),
3065
+ multi: true
3066
+ }
3067
+ ], ngImport: i0, template: "<div class=\"pk-typeahead\" [ngClass]=\"customClass\" [ngStyle]=\"customStyle\">\n <input\n type=\"text\"\n class=\"pk-typeahead-input\"\n [value]=\"value\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n [style]=\"style\"\n autocomplete=\"off\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\">\n\n @if (isOpen && filteredItems.length > 0) {\n <div class=\"pk-typeahead-panel\">\n @for (item of filteredItems; track trackByItem($index, item); let index = $index) {\n <button\n type=\"button\"\n class=\"pk-typeahead-option\"\n [class.active]=\"index === highlightedIndex\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectItem(item)\">\n {{ getItemLabel(item) }}\n </button>\n }\n </div>\n }\n</div>", styles: [".pk-typeahead{position:relative;display:inline-block}.pk-typeahead-input{width:100%;font-size:16px;padding:4px 8px;border:1px solid #999;border-radius:3px;background:#fff}.pk-typeahead-panel{position:absolute;top:calc(100% + 2px);left:0;right:0;z-index:1200;max-height:260px;overflow-y:auto;background:#fff;border:1px solid #c9c9c9;border-radius:4px;box-shadow:0 6px 18px #00000024}.pk-typeahead-option{display:block;width:100%;padding:8px 10px;text-align:left;border:0;background:transparent;cursor:pointer;font-size:14px}.pk-typeahead-option:hover,.pk-typeahead-option.active{background:#eef5ff}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
3068
+ }
3069
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PkTypeaheadComponent, decorators: [{
3070
+ type: Component,
3071
+ args: [{ selector: 'pk-typeahead', standalone: true, imports: [NgClass, NgStyle], providers: [
3072
+ {
3073
+ provide: NG_VALUE_ACCESSOR,
3074
+ useExisting: forwardRef(() => PkTypeaheadComponent),
3075
+ multi: true
3076
+ }
3077
+ ], template: "<div class=\"pk-typeahead\" [ngClass]=\"customClass\" [ngStyle]=\"customStyle\">\n <input\n type=\"text\"\n class=\"pk-typeahead-input\"\n [value]=\"value\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n [style]=\"style\"\n autocomplete=\"off\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\">\n\n @if (isOpen && filteredItems.length > 0) {\n <div class=\"pk-typeahead-panel\">\n @for (item of filteredItems; track trackByItem($index, item); let index = $index) {\n <button\n type=\"button\"\n class=\"pk-typeahead-option\"\n [class.active]=\"index === highlightedIndex\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectItem(item)\">\n {{ getItemLabel(item) }}\n </button>\n }\n </div>\n }\n</div>", styles: [".pk-typeahead{position:relative;display:inline-block}.pk-typeahead-input{width:100%;font-size:16px;padding:4px 8px;border:1px solid #999;border-radius:3px;background:#fff}.pk-typeahead-panel{position:absolute;top:calc(100% + 2px);left:0;right:0;z-index:1200;max-height:260px;overflow-y:auto;background:#fff;border:1px solid #c9c9c9;border-radius:4px;box-shadow:0 6px 18px #00000024}.pk-typeahead-option{display:block;width:100%;padding:8px 10px;text-align:left;border:0;background:transparent;cursor:pointer;font-size:14px}.pk-typeahead-option:hover,.pk-typeahead-option.active{background:#eef5ff}\n"] }]
3078
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { items: [{
3079
+ type: Input
3080
+ }], placeholder: [{
3081
+ type: Input
3082
+ }], disabled: [{
3083
+ type: Input
3084
+ }], style: [{
3085
+ type: Input
3086
+ }], customClass: [{
3087
+ type: Input
3088
+ }], customStyle: [{
3089
+ type: Input
3090
+ }], labelField: [{
3091
+ type: Input
3092
+ }], valueField: [{
3093
+ type: Input
3094
+ }], allowCustomValue: [{
3095
+ type: Input
3096
+ }], maxItems: [{
3097
+ type: Input
3098
+ }], minChars: [{
3099
+ type: Input
3100
+ }], itemSelected: [{
3101
+ type: Output
3102
+ }], onDocumentClick: [{
3103
+ type: HostListener,
3104
+ args: ['document:click', ['$event']]
3105
+ }] } });
3106
+
3107
+ /*
3108
+ * Public API Surface of ngx-pk-ui
3109
+ */
3110
+ // pk-tabs
3111
+
3112
+ /**
3113
+ * Generated bundle index. Do not edit.
3114
+ */
3115
+
3116
+ export { DatepickerService, HolidayService, NowrapDirective, PK_ICONS, PkAlert, PkAlertService, PkAutocompleteComponent, PkDatagridComponent, PkDatagridModule, PkDatepickerComponent, PkDgCellComponent, PkDgColumnComponent, PkDgFooterComponent, PkDgItemsDirective, PkDgPageSizeComponent, PkDgPaginationComponent, PkDgRowComponent, PkDgRowDetailComponent, PkIcon, PkIfExpandedDirective, PkModal, PkModalBody, PkModalFooter, PkModalHeader, PkProgressComponent, PkSelectComponent, PkTab, PkTabs, PkToastr, PkToastrService, PkTreeviewComponent, PkTreeviewModule, PkTreeviewNodeComponent, PkTypeaheadComponent };
3117
+ //# sourceMappingURL=ngx-pk-ui.mjs.map