@brickclay-org/ui 0.0.40 → 0.0.41
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.
- package/fesm2022/brickclay-org-ui.mjs +4035 -0
- package/fesm2022/brickclay-org-ui.mjs.map +1 -0
- package/index.d.ts +857 -0
- package/package.json +26 -15
- package/ASSETS_SETUP.md +0 -59
- package/ng-package.json +0 -29
- package/src/lib/assets/icons.ts +0 -8
- package/src/lib/badge/badge.html +0 -24
- package/src/lib/badge/badge.ts +0 -42
- package/src/lib/brickclay-lib.spec.ts +0 -23
- package/src/lib/brickclay-lib.ts +0 -15
- package/src/lib/button-group/button-group.html +0 -12
- package/src/lib/button-group/button-group.ts +0 -73
- package/src/lib/calender/calendar.module.ts +0 -35
- package/src/lib/calender/components/custom-calendar/custom-calendar.component.css +0 -698
- package/src/lib/calender/components/custom-calendar/custom-calendar.component.html +0 -230
- package/src/lib/calender/components/custom-calendar/custom-calendar.component.spec.ts +0 -23
- package/src/lib/calender/components/custom-calendar/custom-calendar.component.ts +0 -1554
- package/src/lib/calender/components/scheduled-date-picker/scheduled-date-picker.component.css +0 -373
- package/src/lib/calender/components/scheduled-date-picker/scheduled-date-picker.component.html +0 -210
- package/src/lib/calender/components/scheduled-date-picker/scheduled-date-picker.component.ts +0 -361
- package/src/lib/calender/components/time-picker/time-picker.component.css +0 -174
- package/src/lib/calender/components/time-picker/time-picker.component.html +0 -60
- package/src/lib/calender/components/time-picker/time-picker.component.ts +0 -283
- package/src/lib/calender/services/calendar-manager.service.ts +0 -45
- package/src/lib/checkbox/checkbox.html +0 -42
- package/src/lib/checkbox/checkbox.ts +0 -67
- package/src/lib/chips/chips.html +0 -74
- package/src/lib/chips/chips.ts +0 -222
- package/src/lib/grid/components/grid/grid.html +0 -97
- package/src/lib/grid/components/grid/grid.ts +0 -139
- package/src/lib/grid/models/grid.model.ts +0 -20
- package/src/lib/input/input.html +0 -127
- package/src/lib/input/input.ts +0 -394
- package/src/lib/pill/pill.html +0 -24
- package/src/lib/pill/pill.ts +0 -39
- package/src/lib/radio/radio.html +0 -58
- package/src/lib/radio/radio.ts +0 -72
- package/src/lib/select/select.html +0 -111
- package/src/lib/select/select.ts +0 -401
- package/src/lib/spinner/spinner.html +0 -5
- package/src/lib/spinner/spinner.ts +0 -22
- package/src/lib/tabs/tabs.html +0 -28
- package/src/lib/tabs/tabs.ts +0 -48
- package/src/lib/textarea/textarea.html +0 -80
- package/src/lib/textarea/textarea.ts +0 -172
- package/src/lib/toggle/toggle.html +0 -24
- package/src/lib/toggle/toggle.ts +0 -62
- package/src/lib/ui-button/ui-button.html +0 -25
- package/src/lib/ui-button/ui-button.ts +0 -55
- package/src/lib/ui-icon-button/ui-icon-button.html +0 -7
- package/src/lib/ui-icon-button/ui-icon-button.ts +0 -38
- package/src/public-api.ts +0 -43
- package/tsconfig.lib.json +0 -19
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -15
|
@@ -0,0 +1,4035 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Component, EventEmitter, HostListener, ViewChildren, Output, Input, Injectable, NgModule, forwardRef, ViewEncapsulation, Optional, Self, ViewChild, input, model, output, signal, computed, effect, inject, ElementRef } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/common';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
import * as i1$1 from '@angular/forms';
|
|
6
|
+
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
7
|
+
import moment from 'moment';
|
|
8
|
+
import { Subject } from 'rxjs';
|
|
9
|
+
import * as i2 from '@angular/cdk/drag-drop';
|
|
10
|
+
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
|
11
|
+
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
12
|
+
import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
|
|
13
|
+
|
|
14
|
+
// Icon paths that will be resolved relative to the library's assets folder
|
|
15
|
+
// When published to npm, users will need to configure their angular.json to include these assets
|
|
16
|
+
const BrickclayIcons = {
|
|
17
|
+
arrowleft: 'assets/icons/chevron-left.svg',
|
|
18
|
+
arrowRight: 'assets/icons/chevron-right.svg',
|
|
19
|
+
calenderIcon: 'assets/icons/calender.svg',
|
|
20
|
+
timerIcon: 'assets/icons/timer.svg',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
class BrickclayLib {
|
|
24
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BrickclayLib, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
25
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: BrickclayLib, isStandalone: true, selector: "lib-brickclay-lib", ngImport: i0, template: `
|
|
26
|
+
<p>
|
|
27
|
+
brickclay-lib works!
|
|
28
|
+
</p>
|
|
29
|
+
`, isInline: true, styles: [""] });
|
|
30
|
+
}
|
|
31
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BrickclayLib, decorators: [{
|
|
32
|
+
type: Component,
|
|
33
|
+
args: [{ selector: 'lib-brickclay-lib', imports: [], template: `
|
|
34
|
+
<p>
|
|
35
|
+
brickclay-lib works!
|
|
36
|
+
</p>
|
|
37
|
+
` }]
|
|
38
|
+
}] });
|
|
39
|
+
|
|
40
|
+
class BkTimePicker {
|
|
41
|
+
value = '1:00 AM'; // Time in format "H:MM AM/PM"
|
|
42
|
+
label = 'Time';
|
|
43
|
+
placeholder = 'Select time';
|
|
44
|
+
position = 'left';
|
|
45
|
+
pickerId = ''; // Unique ID for this picker
|
|
46
|
+
closePicker = 0; // Close counter from parent (triggers close when changed)
|
|
47
|
+
timeFormat = 12; // Visual mode: 12h or 24h
|
|
48
|
+
showSeconds = false; // Whether to show/edit seconds
|
|
49
|
+
timeChange = new EventEmitter();
|
|
50
|
+
pickerOpened = new EventEmitter(); // Notify parent when opened
|
|
51
|
+
pickerClosed = new EventEmitter(); // Notify parent when closed
|
|
52
|
+
timeScrollElements;
|
|
53
|
+
showPicker = false;
|
|
54
|
+
currentHour = 1;
|
|
55
|
+
currentMinute = 0;
|
|
56
|
+
currentAMPM = 'AM';
|
|
57
|
+
currentSecond = 0;
|
|
58
|
+
brickclayIcons = BrickclayIcons;
|
|
59
|
+
ngOnInit() {
|
|
60
|
+
this.parseTimeValue();
|
|
61
|
+
}
|
|
62
|
+
ngAfterViewInit() {
|
|
63
|
+
if (this.showPicker) {
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
this.scrollToSelectedTimes();
|
|
66
|
+
}, 100);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
parseTimeValue() {
|
|
70
|
+
const parsed = this.parseTimeStringToComponents(this.value);
|
|
71
|
+
this.currentHour = parsed.hour;
|
|
72
|
+
this.currentMinute = parsed.minute;
|
|
73
|
+
this.currentSecond = parsed.second;
|
|
74
|
+
this.currentAMPM = parsed.ampm;
|
|
75
|
+
}
|
|
76
|
+
getHours() {
|
|
77
|
+
// 12-hour: 1-12, 24-hour: 0-23
|
|
78
|
+
if (this.timeFormat === 24) {
|
|
79
|
+
return Array.from({ length: 24 }, (_, i) => i);
|
|
80
|
+
}
|
|
81
|
+
return Array.from({ length: 12 }, (_, i) => i + 1);
|
|
82
|
+
}
|
|
83
|
+
getMinutes() {
|
|
84
|
+
return Array.from({ length: 60 }, (_, i) => i);
|
|
85
|
+
}
|
|
86
|
+
getSeconds() {
|
|
87
|
+
return Array.from({ length: 60 }, (_, i) => i);
|
|
88
|
+
}
|
|
89
|
+
getAMPMOptions() {
|
|
90
|
+
return ['AM', 'PM'];
|
|
91
|
+
}
|
|
92
|
+
parseTimeStringToComponents(timeStr) {
|
|
93
|
+
// Supports:
|
|
94
|
+
// - "H:MM AM/PM"
|
|
95
|
+
// - "H:MM:SS AM/PM"
|
|
96
|
+
// - "HH:MM" (24h)
|
|
97
|
+
// - "HH:MM:SS" (24h)
|
|
98
|
+
if (!timeStr) {
|
|
99
|
+
return {
|
|
100
|
+
hour: this.timeFormat === 24 ? 0 : 12,
|
|
101
|
+
minute: 0,
|
|
102
|
+
second: 0,
|
|
103
|
+
ampm: this.timeFormat === 24 ? '' : 'AM'
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const parts = timeStr.trim().split(' ');
|
|
107
|
+
const timePart = parts[0] || (this.timeFormat === 24 ? '00:00' : '12:00');
|
|
108
|
+
let ampm = (parts[1] || '').toUpperCase();
|
|
109
|
+
const [hoursStr, minutesStr, secondsStr] = timePart.split(':');
|
|
110
|
+
let hour = parseInt(hoursStr || (this.timeFormat === 24 ? '0' : '12'), 10);
|
|
111
|
+
const minute = parseInt(minutesStr || '0', 10);
|
|
112
|
+
const second = parseInt(secondsStr || '0', 10);
|
|
113
|
+
if (this.timeFormat === 24) {
|
|
114
|
+
// In 24-hour mode we ignore AM/PM and keep hour as 0-23
|
|
115
|
+
return {
|
|
116
|
+
hour: isNaN(hour) ? 0 : Math.min(Math.max(hour, 0), 23),
|
|
117
|
+
minute: isNaN(minute) ? 0 : Math.min(Math.max(minute, 0), 59),
|
|
118
|
+
second: isNaN(second) ? 0 : Math.min(Math.max(second, 0), 59),
|
|
119
|
+
ampm: ''
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// 12-hour mode: normalize AM/PM and convert 24h inputs if needed
|
|
123
|
+
let ampmValue = ampm === 'PM' || ampm === 'AM' ? ampm : '';
|
|
124
|
+
if (!ampmValue) {
|
|
125
|
+
// No AM/PM provided -> interpret as 24-hour and convert to 12-hour with AM/PM
|
|
126
|
+
if (hour >= 12) {
|
|
127
|
+
ampmValue = 'PM';
|
|
128
|
+
if (hour > 12)
|
|
129
|
+
hour -= 12;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
ampmValue = 'AM';
|
|
133
|
+
if (hour === 0)
|
|
134
|
+
hour = 12;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Clamp to 1-12 range
|
|
138
|
+
if (hour < 1)
|
|
139
|
+
hour = 1;
|
|
140
|
+
if (hour > 12)
|
|
141
|
+
hour = 12;
|
|
142
|
+
return {
|
|
143
|
+
hour,
|
|
144
|
+
minute: isNaN(minute) ? 0 : Math.min(Math.max(minute, 0), 59),
|
|
145
|
+
second: isNaN(second) ? 0 : Math.min(Math.max(second, 0), 59),
|
|
146
|
+
ampm: ampmValue
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
formatTimeFromComponents(hour, minute, second, ampm) {
|
|
150
|
+
const hStr = hour.toString().padStart(2, '0');
|
|
151
|
+
const minuteStr = minute.toString().padStart(2, '0');
|
|
152
|
+
const secondStr = second.toString().padStart(2, '0');
|
|
153
|
+
if (this.timeFormat === 24) {
|
|
154
|
+
// "HH:mm" or "HH:mm:ss"
|
|
155
|
+
return this.showSeconds
|
|
156
|
+
? `${hStr}:${minuteStr}:${secondStr}`
|
|
157
|
+
: `${hStr}:${minuteStr}`;
|
|
158
|
+
}
|
|
159
|
+
// 12-hour: "H:MM" or "H:MM:SS" with AM/PM
|
|
160
|
+
const displayHour = hour; // already 1-12
|
|
161
|
+
return this.showSeconds
|
|
162
|
+
? `${displayHour}:${minuteStr}:${secondStr} ${ampm}`
|
|
163
|
+
: `${displayHour}:${minuteStr} ${ampm}`;
|
|
164
|
+
}
|
|
165
|
+
togglePicker() {
|
|
166
|
+
if (!this.showPicker) {
|
|
167
|
+
this.showPicker = true;
|
|
168
|
+
this.parseTimeValue();
|
|
169
|
+
this.pickerOpened.emit(this.pickerId);
|
|
170
|
+
setTimeout(() => {
|
|
171
|
+
this.scrollToSelectedTimes();
|
|
172
|
+
}, 100);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.showPicker = false;
|
|
176
|
+
this.pickerClosed.emit(this.pickerId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
onHourChange(hour) {
|
|
180
|
+
this.currentHour = hour;
|
|
181
|
+
this.updateTime();
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
this.scrollToSelectedTimes();
|
|
184
|
+
}, 50);
|
|
185
|
+
}
|
|
186
|
+
onMinuteChange(minute) {
|
|
187
|
+
this.currentMinute = minute;
|
|
188
|
+
this.updateTime();
|
|
189
|
+
setTimeout(() => {
|
|
190
|
+
this.scrollToSelectedTimes();
|
|
191
|
+
}, 50);
|
|
192
|
+
}
|
|
193
|
+
onSecondChange(second) {
|
|
194
|
+
this.currentSecond = second;
|
|
195
|
+
this.updateTime();
|
|
196
|
+
setTimeout(() => {
|
|
197
|
+
this.scrollToSelectedTimes();
|
|
198
|
+
}, 50);
|
|
199
|
+
}
|
|
200
|
+
onAMPMChange(ampm) {
|
|
201
|
+
this.currentAMPM = ampm;
|
|
202
|
+
this.updateTime();
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
this.scrollToSelectedTimes();
|
|
205
|
+
}, 50);
|
|
206
|
+
}
|
|
207
|
+
updateTime() {
|
|
208
|
+
const newTime = this.formatTimeFromComponents(this.currentHour, this.currentMinute, this.currentSecond, this.currentAMPM);
|
|
209
|
+
this.value = newTime;
|
|
210
|
+
this.timeChange.emit(newTime);
|
|
211
|
+
}
|
|
212
|
+
scrollToSelectedTimes() {
|
|
213
|
+
this.timeScrollElements.forEach((elementRef) => {
|
|
214
|
+
const element = elementRef.nativeElement;
|
|
215
|
+
const selectedItem = element.querySelector('.time-item.selected');
|
|
216
|
+
if (selectedItem) {
|
|
217
|
+
const scrollTop = selectedItem.offsetTop - element.offsetHeight / 40 + selectedItem.offsetHeight / 40;
|
|
218
|
+
element.scrollTop = scrollTop;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
onDocumentClick(event) {
|
|
223
|
+
const target = event.target;
|
|
224
|
+
if (!target.closest('.time-picker-wrapper') && this.showPicker) {
|
|
225
|
+
this.showPicker = false;
|
|
226
|
+
this.pickerClosed.emit(this.pickerId);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
previousCloseCounter = 0;
|
|
230
|
+
ngOnChanges(changes) {
|
|
231
|
+
if (changes['value'] && changes['value'].currentValue) {
|
|
232
|
+
this.parseTimeValue();
|
|
233
|
+
}
|
|
234
|
+
if (changes['closePicker'] && this.showPicker) {
|
|
235
|
+
const newCounter = changes['closePicker'].currentValue;
|
|
236
|
+
// If counter increased, close the picker
|
|
237
|
+
if (newCounter > this.previousCloseCounter) {
|
|
238
|
+
this.showPicker = false;
|
|
239
|
+
this.pickerClosed.emit(this.pickerId);
|
|
240
|
+
this.previousCloseCounter = newCounter;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Basic keyboard support on the input (combobox behavior)
|
|
245
|
+
onInputKeydown(event) {
|
|
246
|
+
const key = event.key;
|
|
247
|
+
if (key === 'Enter' || key === ' ') {
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
this.togglePicker();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (key === 'Escape' && this.showPicker) {
|
|
253
|
+
this.showPicker = false;
|
|
254
|
+
this.pickerClosed.emit(this.pickerId);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// Simple hour increment/decrement when closed
|
|
258
|
+
if (!this.showPicker && (key === 'ArrowUp' || key === 'ArrowDown')) {
|
|
259
|
+
event.preventDefault();
|
|
260
|
+
if (this.timeFormat === 24) {
|
|
261
|
+
if (key === 'ArrowUp') {
|
|
262
|
+
this.currentHour = (this.currentHour + 1) % 24;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
this.currentHour = this.currentHour <= 0 ? 23 : this.currentHour - 1;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
if (key === 'ArrowUp') {
|
|
270
|
+
this.currentHour = this.currentHour >= 12 ? 1 : this.currentHour + 1;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
this.currentHour = this.currentHour <= 1 ? 12 : this.currentHour - 1;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
this.updateTime();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkTimePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
280
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: BkTimePicker, isStandalone: true, selector: "bk-time-picker", inputs: { value: "value", label: "label", placeholder: "placeholder", position: "position", pickerId: "pickerId", closePicker: "closePicker", timeFormat: "timeFormat", showSeconds: "showSeconds" }, outputs: { timeChange: "timeChange", pickerOpened: "pickerOpened", pickerClosed: "pickerClosed" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, viewQueries: [{ propertyName: "timeScrollElements", predicate: ["timeScroll"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"time-picker-wrapper\">\r\n <div class=\"time-input-group\">\r\n <label *ngIf=\"label\">{{ label }}</label>\r\n <div class=\"time-input-wrapper\">\r\n <input\r\n type=\"text\"\r\n class=\"time-input\"\r\n [value]=\"value\"\r\n [placeholder]=\"placeholder\"\r\n readonly\r\n (click)=\"togglePicker()\">\r\n <span class=\"time-icon\">\r\n <img alt=\"timer\" class=\"timer-icon\" [src]='brickclayIcons.timerIcon'/>\r\n </span>\r\n <div class=\"custom-time-picker-dropdown\" *ngIf=\"showPicker\">\r\n <div class=\"custom-time-picker\" [ngClass]=\"position === 'left' ? 'left-position' : 'right-position'\">\r\n <!-- Hours Column -->\r\n <div class=\"time-column\">\r\n <div class=\"time-scroll\" #timeScroll>\r\n <div\r\n *ngFor=\"let h of getHours()\"\r\n class=\"time-item\"\r\n [class.selected]=\"currentHour === h\"\r\n (click)=\"onHourChange(h)\">\r\n {{ h.toString().padStart(2, '0') }}\r\n </div>\r\n </div>\r\n </div>\r\n <span class=\"time-separator\">:</span>\r\n <!-- Minutes Column -->\r\n <div class=\"time-column\">\r\n <div class=\"time-scroll\" #timeScroll>\r\n <div\r\n *ngFor=\"let m of getMinutes()\"\r\n class=\"time-item\"\r\n [class.selected]=\"currentMinute === m\"\r\n (click)=\"onMinuteChange(m)\">\r\n {{ m.toString().padStart(2, '0') }}\r\n </div>\r\n </div>\r\n </div>\r\n <span class=\"time-separator\">:</span>\r\n <!-- AM/PM Column -->\r\n <div class=\"time-column ampm-column\">\r\n <div class=\"time-scroll\" #timeScroll>\r\n <div\r\n *ngFor=\"let ap of getAMPMOptions()\"\r\n class=\"time-item\"\r\n [class.selected]=\"currentAMPM === ap\"\r\n (click)=\"onAMPMChange(ap)\">\r\n {{ ap }}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n", styles: [".time-picker-wrapper{width:100%;font-family:Inter,sans-serif}.time-input-group{display:flex;flex-direction:column;gap:4px}.time-input-group label{font-size:11px;font-weight:500;color:#15191e;text-transform:uppercase;letter-spacing:-.28px}.time-input-wrapper{position:relative;display:flex;align-items:center}.time-input{padding:8px 40px 8px 12px;border:1px solid #d1d5db;border-radius:4px;font-size:12px;font-family:Inter,sans-serif;color:#6f737b;background:#fff;transition:all .2s;width:100%;box-sizing:border-box;cursor:pointer}.time-input:focus{outline:none;border-color:#111827;box-shadow:0 0 0 3px #1118271a}.time-input:hover{border-color:#9ca3af}.time-icon{position:absolute;right:12px;font-size:16px;pointer-events:none;color:#9ca3af;cursor:pointer}.custom-time-picker-wrapper{width:100%;display:flex;justify-content:center}.custom-time-picker{display:flex;gap:8px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:10px;box-shadow:0 4px 12px #00000026;width:182px;position:absolute;top:calc(100% + 4px);z-index:1000}.custom-time-picker.left-position{left:0}.custom-time-picker.right-position{right:0}.time-column{display:flex;flex-direction:column;position:relative}.time-scroll{display:flex;flex-direction:column;max-height:95px;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:#cbd5e1 transparent;scrollbar-width:none;-ms-overflow-style:none}.time-scroll::-webkit-scrollbar{display:none}.time-scroll::-webkit-scrollbar-track{background:transparent}.time-scroll::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:2px}.time-scroll::-webkit-scrollbar-thumb:hover{background:#94a3b8}.time-item{min-width:40px;width:40px;height:32px;min-height:32px;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:400;color:#374151;cursor:pointer;border-radius:4px;transition:all .15s ease;-webkit-user-select:none;user-select:none;font-family:Inter,sans-serif}.time-item:hover{background:#f3f4f6}.time-item.selected{background:#111827;color:#fff;font-weight:500;box-shadow:0 1px 2px #2563eb4d}.ampm-column .time-item{min-width:40px;width:40px}.time-separator{font-size:16px;font-weight:600;color:#6b7280;margin:5px 0 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
281
|
+
}
|
|
282
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkTimePicker, decorators: [{
|
|
283
|
+
type: Component,
|
|
284
|
+
args: [{ selector: 'bk-time-picker', standalone: true, imports: [CommonModule], template: "<div class=\"time-picker-wrapper\">\r\n <div class=\"time-input-group\">\r\n <label *ngIf=\"label\">{{ label }}</label>\r\n <div class=\"time-input-wrapper\">\r\n <input\r\n type=\"text\"\r\n class=\"time-input\"\r\n [value]=\"value\"\r\n [placeholder]=\"placeholder\"\r\n readonly\r\n (click)=\"togglePicker()\">\r\n <span class=\"time-icon\">\r\n <img alt=\"timer\" class=\"timer-icon\" [src]='brickclayIcons.timerIcon'/>\r\n </span>\r\n <div class=\"custom-time-picker-dropdown\" *ngIf=\"showPicker\">\r\n <div class=\"custom-time-picker\" [ngClass]=\"position === 'left' ? 'left-position' : 'right-position'\">\r\n <!-- Hours Column -->\r\n <div class=\"time-column\">\r\n <div class=\"time-scroll\" #timeScroll>\r\n <div\r\n *ngFor=\"let h of getHours()\"\r\n class=\"time-item\"\r\n [class.selected]=\"currentHour === h\"\r\n (click)=\"onHourChange(h)\">\r\n {{ h.toString().padStart(2, '0') }}\r\n </div>\r\n </div>\r\n </div>\r\n <span class=\"time-separator\">:</span>\r\n <!-- Minutes Column -->\r\n <div class=\"time-column\">\r\n <div class=\"time-scroll\" #timeScroll>\r\n <div\r\n *ngFor=\"let m of getMinutes()\"\r\n class=\"time-item\"\r\n [class.selected]=\"currentMinute === m\"\r\n (click)=\"onMinuteChange(m)\">\r\n {{ m.toString().padStart(2, '0') }}\r\n </div>\r\n </div>\r\n </div>\r\n <span class=\"time-separator\">:</span>\r\n <!-- AM/PM Column -->\r\n <div class=\"time-column ampm-column\">\r\n <div class=\"time-scroll\" #timeScroll>\r\n <div\r\n *ngFor=\"let ap of getAMPMOptions()\"\r\n class=\"time-item\"\r\n [class.selected]=\"currentAMPM === ap\"\r\n (click)=\"onAMPMChange(ap)\">\r\n {{ ap }}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n", styles: [".time-picker-wrapper{width:100%;font-family:Inter,sans-serif}.time-input-group{display:flex;flex-direction:column;gap:4px}.time-input-group label{font-size:11px;font-weight:500;color:#15191e;text-transform:uppercase;letter-spacing:-.28px}.time-input-wrapper{position:relative;display:flex;align-items:center}.time-input{padding:8px 40px 8px 12px;border:1px solid #d1d5db;border-radius:4px;font-size:12px;font-family:Inter,sans-serif;color:#6f737b;background:#fff;transition:all .2s;width:100%;box-sizing:border-box;cursor:pointer}.time-input:focus{outline:none;border-color:#111827;box-shadow:0 0 0 3px #1118271a}.time-input:hover{border-color:#9ca3af}.time-icon{position:absolute;right:12px;font-size:16px;pointer-events:none;color:#9ca3af;cursor:pointer}.custom-time-picker-wrapper{width:100%;display:flex;justify-content:center}.custom-time-picker{display:flex;gap:8px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:10px;box-shadow:0 4px 12px #00000026;width:182px;position:absolute;top:calc(100% + 4px);z-index:1000}.custom-time-picker.left-position{left:0}.custom-time-picker.right-position{right:0}.time-column{display:flex;flex-direction:column;position:relative}.time-scroll{display:flex;flex-direction:column;max-height:95px;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:#cbd5e1 transparent;scrollbar-width:none;-ms-overflow-style:none}.time-scroll::-webkit-scrollbar{display:none}.time-scroll::-webkit-scrollbar-track{background:transparent}.time-scroll::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:2px}.time-scroll::-webkit-scrollbar-thumb:hover{background:#94a3b8}.time-item{min-width:40px;width:40px;height:32px;min-height:32px;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:400;color:#374151;cursor:pointer;border-radius:4px;transition:all .15s ease;-webkit-user-select:none;user-select:none;font-family:Inter,sans-serif}.time-item:hover{background:#f3f4f6}.time-item.selected{background:#111827;color:#fff;font-weight:500;box-shadow:0 1px 2px #2563eb4d}.ampm-column .time-item{min-width:40px;width:40px}.time-separator{font-size:16px;font-weight:600;color:#6b7280;margin:5px 0 0}\n"] }]
|
|
285
|
+
}], propDecorators: { value: [{
|
|
286
|
+
type: Input
|
|
287
|
+
}], label: [{
|
|
288
|
+
type: Input
|
|
289
|
+
}], placeholder: [{
|
|
290
|
+
type: Input
|
|
291
|
+
}], position: [{
|
|
292
|
+
type: Input
|
|
293
|
+
}], pickerId: [{
|
|
294
|
+
type: Input
|
|
295
|
+
}], closePicker: [{
|
|
296
|
+
type: Input
|
|
297
|
+
}], timeFormat: [{
|
|
298
|
+
type: Input
|
|
299
|
+
}], showSeconds: [{
|
|
300
|
+
type: Input
|
|
301
|
+
}], timeChange: [{
|
|
302
|
+
type: Output
|
|
303
|
+
}], pickerOpened: [{
|
|
304
|
+
type: Output
|
|
305
|
+
}], pickerClosed: [{
|
|
306
|
+
type: Output
|
|
307
|
+
}], timeScrollElements: [{
|
|
308
|
+
type: ViewChildren,
|
|
309
|
+
args: ['timeScroll']
|
|
310
|
+
}], onDocumentClick: [{
|
|
311
|
+
type: HostListener,
|
|
312
|
+
args: ['document:click', ['$event']]
|
|
313
|
+
}] } });
|
|
314
|
+
|
|
315
|
+
class CalendarManagerService {
|
|
316
|
+
calendarInstances = new Set();
|
|
317
|
+
closeAllSubject = new Subject();
|
|
318
|
+
closeAll$ = this.closeAllSubject.asObservable();
|
|
319
|
+
/**
|
|
320
|
+
* Register a calendar instance with its close function
|
|
321
|
+
*/
|
|
322
|
+
register(closeFn) {
|
|
323
|
+
this.calendarInstances.add(closeFn);
|
|
324
|
+
// Return unregister function
|
|
325
|
+
return () => {
|
|
326
|
+
this.calendarInstances.delete(closeFn);
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Close all calendars except the one being opened
|
|
331
|
+
*/
|
|
332
|
+
closeAllExcept(exceptCloseFn) {
|
|
333
|
+
this.calendarInstances.forEach(closeFn => {
|
|
334
|
+
if (closeFn !== exceptCloseFn) {
|
|
335
|
+
closeFn();
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Close all calendars
|
|
341
|
+
*/
|
|
342
|
+
closeAll() {
|
|
343
|
+
this.closeAllSubject.next();
|
|
344
|
+
this.calendarInstances.forEach(closeFn => closeFn());
|
|
345
|
+
}
|
|
346
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CalendarManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
347
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CalendarManagerService, providedIn: 'root' });
|
|
348
|
+
}
|
|
349
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CalendarManagerService, decorators: [{
|
|
350
|
+
type: Injectable,
|
|
351
|
+
args: [{
|
|
352
|
+
providedIn: 'root'
|
|
353
|
+
}]
|
|
354
|
+
}] });
|
|
355
|
+
|
|
356
|
+
class BkCustomCalendar {
|
|
357
|
+
calendarManager;
|
|
358
|
+
// Basic Options
|
|
359
|
+
enableTimepicker = false;
|
|
360
|
+
autoApply = false;
|
|
361
|
+
closeOnAutoApply = false;
|
|
362
|
+
showCancel = true;
|
|
363
|
+
linkedCalendars = false;
|
|
364
|
+
singleDatePicker = false;
|
|
365
|
+
showWeekNumbers = false;
|
|
366
|
+
showISOWeekNumbers = false;
|
|
367
|
+
customRangeDirection = false;
|
|
368
|
+
lockStartDate = false;
|
|
369
|
+
position = 'left';
|
|
370
|
+
drop = 'down';
|
|
371
|
+
dualCalendar = false;
|
|
372
|
+
showRanges = true;
|
|
373
|
+
timeFormat = 24;
|
|
374
|
+
enableSeconds = false;
|
|
375
|
+
customRanges;
|
|
376
|
+
multiDateSelection = false; // NEW: Enable multi-date selection
|
|
377
|
+
maxDate; // NEW: Maximum selectable date
|
|
378
|
+
minDate; // NEW: Minimum selectable date
|
|
379
|
+
placeholder = 'Select date range'; // NEW: Custom placeholder
|
|
380
|
+
opens = 'left'; // NEW: Popup position
|
|
381
|
+
inline = false; // NEW: Always show calendar inline (no popup)
|
|
382
|
+
isDisplayCrossIcon = true; // NEW: Show/Hide clear (X) icon
|
|
383
|
+
selected = new EventEmitter();
|
|
384
|
+
opened = new EventEmitter();
|
|
385
|
+
closed = new EventEmitter();
|
|
386
|
+
/**
|
|
387
|
+
* External value passed from parent. If provided, component will select these dates on init / change.
|
|
388
|
+
* Accepts { startDate: Date|null, endDate: Date|null, selectedDates?: Date[] }
|
|
389
|
+
*/
|
|
390
|
+
selectedValue = null;
|
|
391
|
+
/** Optional display format for the input value. Uses moment formatting tokens. */
|
|
392
|
+
displayFormat = 'MM/DD/YYYY';
|
|
393
|
+
brickclayIcons = BrickclayIcons;
|
|
394
|
+
show = false;
|
|
395
|
+
today = new Date();
|
|
396
|
+
month = this.today.getMonth();
|
|
397
|
+
year = this.today.getFullYear();
|
|
398
|
+
calendar = [];
|
|
399
|
+
leftMonth;
|
|
400
|
+
leftYear;
|
|
401
|
+
rightMonth;
|
|
402
|
+
rightYear;
|
|
403
|
+
leftCalendar = [];
|
|
404
|
+
rightCalendar = [];
|
|
405
|
+
startDate = null;
|
|
406
|
+
endDate = null;
|
|
407
|
+
selectedDates = []; // NEW: For multi-date selection
|
|
408
|
+
disableHighlight = false;
|
|
409
|
+
hoveredDate = null; // For hover preview
|
|
410
|
+
// Track raw input values for minutes to allow free typing
|
|
411
|
+
minuteInputValues = {};
|
|
412
|
+
// Time picker for single calendar (12-hour format: 1-12)
|
|
413
|
+
selectedHour = 1;
|
|
414
|
+
selectedMinute = 0;
|
|
415
|
+
selectedSecond = 0;
|
|
416
|
+
selectedAMPM = 'AM';
|
|
417
|
+
// NEW: Separate time pickers for dual calendar (12-hour format: 1-12)
|
|
418
|
+
startHour = 1;
|
|
419
|
+
startMinute = 0;
|
|
420
|
+
startSecond = 0;
|
|
421
|
+
startAMPM = 'AM';
|
|
422
|
+
endHour = 2;
|
|
423
|
+
endMinute = 0;
|
|
424
|
+
endSecond = 0;
|
|
425
|
+
endAMPM = 'AM';
|
|
426
|
+
// Track open time-picker within this calendar (for single-open behavior)
|
|
427
|
+
openTimePickerId = null;
|
|
428
|
+
closePickerCounter = {};
|
|
429
|
+
defaultRanges = {};
|
|
430
|
+
activeRange = null; // Track which range is currently active
|
|
431
|
+
rangeOrder = []; // Maintain order of ranges
|
|
432
|
+
unregisterFn;
|
|
433
|
+
closeAllSubscription;
|
|
434
|
+
closeFn;
|
|
435
|
+
constructor(calendarManager) {
|
|
436
|
+
this.calendarManager = calendarManager;
|
|
437
|
+
}
|
|
438
|
+
onClickOutside(event) {
|
|
439
|
+
// Don't handle click outside if inline mode is enabled
|
|
440
|
+
if (this.inline) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const target = event.target;
|
|
444
|
+
if (this.show && !target.closest('.calendar-container')) {
|
|
445
|
+
this.close();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
ngOnInit() {
|
|
449
|
+
if (!this.customRanges) {
|
|
450
|
+
this.initializeDefaultRanges();
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
// If customRanges is provided via @Input, set the order based on the keys
|
|
454
|
+
// Maintain the desired order if keys match, otherwise use provided order
|
|
455
|
+
const desiredOrder = ['Today', 'Yesterday', 'Last 7 Days', 'Last 30 Days', 'This Month', 'Last Month', 'Custom Range'];
|
|
456
|
+
const providedKeys = Object.keys(this.customRanges);
|
|
457
|
+
// Check if Custom Range exists, if not add it
|
|
458
|
+
if (!this.customRanges['Custom Range']) {
|
|
459
|
+
this.customRanges['Custom Range'] = { start: new Date(), end: new Date() };
|
|
460
|
+
}
|
|
461
|
+
// Build order: first add desired order items that exist, then add any remaining
|
|
462
|
+
this.rangeOrder = desiredOrder.filter(key => providedKeys.includes(key) || key === 'Custom Range');
|
|
463
|
+
const remaining = providedKeys.filter(key => !this.rangeOrder.includes(key));
|
|
464
|
+
this.rangeOrder = [...this.rangeOrder, ...remaining];
|
|
465
|
+
}
|
|
466
|
+
if (this.dualCalendar)
|
|
467
|
+
this.initializeDual();
|
|
468
|
+
else
|
|
469
|
+
this.generateCalendar();
|
|
470
|
+
// Initialize time from existing dates if available
|
|
471
|
+
if (this.startDate) {
|
|
472
|
+
this.initializeTimeFromDate(this.startDate, true);
|
|
473
|
+
}
|
|
474
|
+
if (this.endDate) {
|
|
475
|
+
this.initializeTimeFromDate(this.endDate, false);
|
|
476
|
+
}
|
|
477
|
+
// Check if current dates match any predefined range
|
|
478
|
+
if (this.startDate && this.endDate) {
|
|
479
|
+
this.checkAndSetActiveRange();
|
|
480
|
+
}
|
|
481
|
+
// If inline mode, always show calendar
|
|
482
|
+
if (this.inline) {
|
|
483
|
+
this.show = true;
|
|
484
|
+
}
|
|
485
|
+
// Register this calendar instance with the manager service
|
|
486
|
+
this.closeFn = () => {
|
|
487
|
+
if (this.show && !this.inline) {
|
|
488
|
+
this.close();
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
this.unregisterFn = this.calendarManager.register(this.closeFn);
|
|
492
|
+
// Subscribe to close all events (skip if inline)
|
|
493
|
+
this.closeAllSubscription = this.calendarManager.closeAll$.subscribe(() => {
|
|
494
|
+
if (this.show && !this.inline) {
|
|
495
|
+
this.close();
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
ngOnChanges(changes) {
|
|
500
|
+
if (changes['selectedValue'] && this.selectedValue) {
|
|
501
|
+
// Normalize incoming values to Date or null
|
|
502
|
+
const s = this.selectedValue;
|
|
503
|
+
this.startDate = s.startDate ? new Date(s.startDate) : null;
|
|
504
|
+
this.endDate = s.endDate ? new Date(s.endDate) : null;
|
|
505
|
+
this.selectedDates = (s.selectedDates || []).map((d) => new Date(d));
|
|
506
|
+
// Update calendar month/year to show the start date (or end date if start missing)
|
|
507
|
+
const focusDate = this.startDate ?? this.endDate ?? new Date();
|
|
508
|
+
this.month = focusDate.getMonth();
|
|
509
|
+
this.year = focusDate.getFullYear();
|
|
510
|
+
if (this.dualCalendar) {
|
|
511
|
+
this.initializeDual();
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
this.generateCalendar();
|
|
515
|
+
}
|
|
516
|
+
// Re-evaluate active range if any
|
|
517
|
+
this.checkAndSetActiveRange();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
ngOnDestroy() {
|
|
521
|
+
// Unregister this calendar instance
|
|
522
|
+
if (this.unregisterFn) {
|
|
523
|
+
this.unregisterFn();
|
|
524
|
+
}
|
|
525
|
+
// Unsubscribe from close all events
|
|
526
|
+
if (this.closeAllSubscription) {
|
|
527
|
+
this.closeAllSubscription.unsubscribe();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
checkAndSetActiveRange() {
|
|
531
|
+
if (!this.customRanges || !this.startDate || !this.endDate)
|
|
532
|
+
return;
|
|
533
|
+
// Normalize dates for comparison (ignore time)
|
|
534
|
+
const normalizeDate = (date) => {
|
|
535
|
+
const d = new Date(date);
|
|
536
|
+
d.setHours(0, 0, 0, 0);
|
|
537
|
+
return d;
|
|
538
|
+
};
|
|
539
|
+
const start = normalizeDate(this.startDate);
|
|
540
|
+
const end = normalizeDate(this.endDate);
|
|
541
|
+
// Check each range (except Custom Range)
|
|
542
|
+
for (const key of this.rangeOrder) {
|
|
543
|
+
if (key === 'Custom Range')
|
|
544
|
+
continue;
|
|
545
|
+
const range = this.customRanges[key];
|
|
546
|
+
if (range) {
|
|
547
|
+
const rangeStart = normalizeDate(range.start);
|
|
548
|
+
const rangeEnd = normalizeDate(range.end);
|
|
549
|
+
if (start.getTime() === rangeStart.getTime() && end.getTime() === rangeEnd.getTime()) {
|
|
550
|
+
this.activeRange = key;
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// If no match found, it's a custom range
|
|
556
|
+
this.activeRange = 'Custom Range';
|
|
557
|
+
}
|
|
558
|
+
initializeDefaultRanges() {
|
|
559
|
+
const today = new Date();
|
|
560
|
+
this.customRanges = {
|
|
561
|
+
'Today': { start: new Date(today.getFullYear(), today.getMonth(), today.getDate()), end: new Date(today.getFullYear(), today.getMonth(), today.getDate()) },
|
|
562
|
+
'Yesterday': { start: this.addDays(today, -1), end: this.addDays(today, -1) },
|
|
563
|
+
'Last 7 Days': { start: this.addDays(today, -6), end: today },
|
|
564
|
+
'Last 30 Days': { start: this.addDays(today, -29), end: today },
|
|
565
|
+
'This Month': { start: new Date(today.getFullYear(), today.getMonth(), 1), end: today },
|
|
566
|
+
'Last Month': { start: new Date(today.getFullYear(), today.getMonth() - 1, 1), end: new Date(today.getFullYear(), today.getMonth(), 0) },
|
|
567
|
+
'Custom Range': { start: new Date(), end: new Date() }, // Placeholder, won't be used for selection
|
|
568
|
+
};
|
|
569
|
+
// Set the order of ranges
|
|
570
|
+
this.rangeOrder = ['Today', 'Yesterday', 'Last 7 Days', 'Last 30 Days', 'This Month', 'Last Month', 'Custom Range'];
|
|
571
|
+
}
|
|
572
|
+
initializeTimeFromDate(date, isStart) {
|
|
573
|
+
// Always use 12-hour format
|
|
574
|
+
const hours24 = date.getHours();
|
|
575
|
+
const minutes = date.getMinutes();
|
|
576
|
+
const seconds = date.getSeconds();
|
|
577
|
+
if (isStart) {
|
|
578
|
+
this.startMinute = minutes;
|
|
579
|
+
this.startSecond = seconds;
|
|
580
|
+
if (hours24 >= 12) {
|
|
581
|
+
this.startAMPM = 'PM';
|
|
582
|
+
this.startHour = hours24 > 12 ? hours24 - 12 : 12;
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
this.startAMPM = 'AM';
|
|
586
|
+
this.startHour = hours24 === 0 ? 12 : hours24;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
this.endMinute = minutes;
|
|
591
|
+
this.endSecond = seconds;
|
|
592
|
+
if (hours24 >= 12) {
|
|
593
|
+
this.endAMPM = 'PM';
|
|
594
|
+
this.endHour = hours24 > 12 ? hours24 - 12 : 12;
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
this.endAMPM = 'AM';
|
|
598
|
+
this.endHour = hours24 === 0 ? 12 : hours24;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
toggle() {
|
|
603
|
+
// Don't toggle if inline mode is enabled
|
|
604
|
+
if (this.inline) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const wasOpen = this.show;
|
|
608
|
+
this.show = !this.show;
|
|
609
|
+
if (this.show) {
|
|
610
|
+
// If opening, close all other calendars first
|
|
611
|
+
if (!wasOpen && this.closeFn) {
|
|
612
|
+
this.calendarManager.closeAllExcept(this.closeFn);
|
|
613
|
+
}
|
|
614
|
+
this.disableHighlight = false;
|
|
615
|
+
this.opened.emit();
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
this.closed.emit();
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
close() {
|
|
622
|
+
// Don't close if inline mode is enabled
|
|
623
|
+
if (this.inline) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
this.show = false;
|
|
627
|
+
this.closed.emit();
|
|
628
|
+
}
|
|
629
|
+
onDateHover(day, fromRight = false) {
|
|
630
|
+
if (!day || this.singleDatePicker || this.multiDateSelection) {
|
|
631
|
+
this.hoveredDate = null;
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
// Only show hover preview if start date is selected but end date is not
|
|
635
|
+
if (!this.startDate || this.endDate) {
|
|
636
|
+
this.hoveredDate = null;
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (!this.dualCalendar) {
|
|
640
|
+
this.hoveredDate = new Date(this.year, this.month, day);
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
this.hoveredDate = fromRight
|
|
644
|
+
? new Date(this.rightYear, this.rightMonth, day)
|
|
645
|
+
: new Date(this.leftYear, this.leftMonth, day);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
onDateLeave() {
|
|
649
|
+
this.hoveredDate = null;
|
|
650
|
+
}
|
|
651
|
+
selectDate(day, fromRight = false) {
|
|
652
|
+
if (!day)
|
|
653
|
+
return;
|
|
654
|
+
let selected;
|
|
655
|
+
if (!this.dualCalendar) {
|
|
656
|
+
selected = new Date(this.year, this.month, day);
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
selected = fromRight
|
|
660
|
+
? new Date(this.rightYear, this.rightMonth, day)
|
|
661
|
+
: new Date(this.leftYear, this.leftMonth, day);
|
|
662
|
+
}
|
|
663
|
+
// Clear hover on selection
|
|
664
|
+
this.hoveredDate = null;
|
|
665
|
+
// Check min/max date constraints
|
|
666
|
+
if (this.minDate && selected < this.minDate)
|
|
667
|
+
return;
|
|
668
|
+
if (this.maxDate && selected > this.maxDate)
|
|
669
|
+
return;
|
|
670
|
+
// Multi-date selection mode
|
|
671
|
+
if (this.multiDateSelection) {
|
|
672
|
+
this.handleMultiDateSelection(selected);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
// Apply time if timepicker is enabled (convert 12-hour to 24-hour)
|
|
676
|
+
if (this.enableTimepicker) {
|
|
677
|
+
if (this.dualCalendar) {
|
|
678
|
+
// For dual calendar, use separate start/end times
|
|
679
|
+
// If no startDate OR endDate exists, we're selecting start date
|
|
680
|
+
const isStart = !this.startDate || !!this.endDate;
|
|
681
|
+
this.applyTimeToDate(selected, isStart);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
// For single calendar, always use selected time for start
|
|
685
|
+
this.applyTimeToDate(selected, true);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
// Single date picker mode
|
|
689
|
+
if (this.singleDatePicker) {
|
|
690
|
+
this.startDate = selected;
|
|
691
|
+
this.endDate = null;
|
|
692
|
+
// Activate Custom Range when manually selecting dates
|
|
693
|
+
this.activeRange = 'Custom Range';
|
|
694
|
+
// Apply time immediately if timepicker is enabled
|
|
695
|
+
if (this.enableTimepicker) {
|
|
696
|
+
this.applyTimeToDate(this.startDate, true);
|
|
697
|
+
}
|
|
698
|
+
if (this.autoApply) {
|
|
699
|
+
this.apply();
|
|
700
|
+
if (this.closeOnAutoApply && !this.inline)
|
|
701
|
+
this.close();
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
// Always emit selection event even if autoApply is false (especially for inline calendars)
|
|
705
|
+
this.emitSelection();
|
|
706
|
+
}
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
// Range selection mode
|
|
710
|
+
if (!this.startDate || this.endDate) {
|
|
711
|
+
this.startDate = selected;
|
|
712
|
+
this.endDate = null;
|
|
713
|
+
// Activate Custom Range when manually selecting dates
|
|
714
|
+
this.activeRange = 'Custom Range';
|
|
715
|
+
// Keep left calendar on the selected month for better UX
|
|
716
|
+
if (this.dualCalendar) {
|
|
717
|
+
this.leftMonth = selected.getMonth();
|
|
718
|
+
this.leftYear = selected.getFullYear();
|
|
719
|
+
// Reset right calendar to original position (next month after left) when end date is cleared
|
|
720
|
+
this.rightMonth = this.leftMonth + 1;
|
|
721
|
+
this.rightYear = this.leftYear;
|
|
722
|
+
if (this.rightMonth > 11) {
|
|
723
|
+
this.rightMonth = 0;
|
|
724
|
+
this.rightYear++;
|
|
725
|
+
}
|
|
726
|
+
this.generateDualCalendars();
|
|
727
|
+
}
|
|
728
|
+
// Don't overwrite time picker values - keep current values and apply them to the date
|
|
729
|
+
// Time picker values are already set by user, we just apply them to the selected date
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
if (selected < this.startDate && !this.customRangeDirection) {
|
|
733
|
+
this.endDate = this.startDate;
|
|
734
|
+
this.startDate = selected;
|
|
735
|
+
// Activate Custom Range when manually selecting dates
|
|
736
|
+
this.activeRange = 'Custom Range';
|
|
737
|
+
// Swap times if needed
|
|
738
|
+
if (this.dualCalendar && this.enableTimepicker) {
|
|
739
|
+
[this.startHour, this.endHour] = [this.endHour, this.startHour];
|
|
740
|
+
[this.startMinute, this.endMinute] = [this.endMinute, this.startMinute];
|
|
741
|
+
[this.startSecond, this.endSecond] = [this.endSecond, this.startSecond];
|
|
742
|
+
[this.startAMPM, this.endAMPM] = [this.endAMPM, this.startAMPM];
|
|
743
|
+
}
|
|
744
|
+
// Keep left calendar on the selected month
|
|
745
|
+
if (this.dualCalendar) {
|
|
746
|
+
this.leftMonth = selected.getMonth();
|
|
747
|
+
this.leftYear = selected.getFullYear();
|
|
748
|
+
this.leftCalendar = this.buildCalendar(this.leftYear, this.leftMonth);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
this.endDate = selected;
|
|
753
|
+
// Activate Custom Range when manually selecting dates
|
|
754
|
+
this.activeRange = 'Custom Range';
|
|
755
|
+
// Only move right calendar if end date is in a different month than start date
|
|
756
|
+
if (this.dualCalendar) {
|
|
757
|
+
if (this.startDate) {
|
|
758
|
+
const startMonth = this.startDate.getMonth();
|
|
759
|
+
const startYear = this.startDate.getFullYear();
|
|
760
|
+
const endMonth = selected.getMonth();
|
|
761
|
+
const endYear = selected.getFullYear();
|
|
762
|
+
// Only move right calendar if end date is in a different month
|
|
763
|
+
if (endMonth !== startMonth || endYear !== startYear) {
|
|
764
|
+
this.rightMonth = endMonth;
|
|
765
|
+
this.rightYear = endYear;
|
|
766
|
+
this.rightCalendar = this.buildCalendar(this.rightYear, this.rightMonth);
|
|
767
|
+
}
|
|
768
|
+
// If both dates are in same month, keep right calendar in its current position
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
// If no start date, move right calendar to end date month
|
|
772
|
+
this.rightMonth = selected.getMonth();
|
|
773
|
+
this.rightYear = selected.getFullYear();
|
|
774
|
+
this.rightCalendar = this.buildCalendar(this.rightYear, this.rightMonth);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
// Don't overwrite time picker values - keep current end time values
|
|
778
|
+
// Time picker values are already set by user
|
|
779
|
+
}
|
|
780
|
+
if (this.autoApply) {
|
|
781
|
+
this.apply();
|
|
782
|
+
if (this.closeOnAutoApply && !this.inline)
|
|
783
|
+
this.close();
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
// Check if the selection matches a predefined range
|
|
787
|
+
this.checkAndSetActiveRange();
|
|
788
|
+
// Always emit selection event for inline calendars
|
|
789
|
+
if (this.inline) {
|
|
790
|
+
this.emitSelection();
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
handleMultiDateSelection(selected) {
|
|
796
|
+
const dateStr = this.getDateString(selected);
|
|
797
|
+
const existingIndex = this.selectedDates.findIndex(d => this.getDateString(d) === dateStr);
|
|
798
|
+
if (existingIndex >= 0) {
|
|
799
|
+
// Deselect if already selected
|
|
800
|
+
this.selectedDates.splice(existingIndex, 1);
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
// Add to selection
|
|
804
|
+
this.selectedDates.push(new Date(selected));
|
|
805
|
+
this.selectedDates.sort((a, b) => a.getTime() - b.getTime());
|
|
806
|
+
}
|
|
807
|
+
// Update startDate and endDate for compatibility
|
|
808
|
+
if (this.selectedDates.length > 0) {
|
|
809
|
+
this.startDate = new Date(this.selectedDates[0]);
|
|
810
|
+
this.endDate = new Date(this.selectedDates[this.selectedDates.length - 1]);
|
|
811
|
+
// Activate Custom Range when manually selecting dates
|
|
812
|
+
this.activeRange = 'Custom Range';
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
this.startDate = null;
|
|
816
|
+
this.endDate = null;
|
|
817
|
+
this.activeRange = null;
|
|
818
|
+
}
|
|
819
|
+
// Always emit selection event for inline calendars or when autoApply is true
|
|
820
|
+
if (this.autoApply || this.inline) {
|
|
821
|
+
this.emitSelection();
|
|
822
|
+
if (this.closeOnAutoApply && !this.inline)
|
|
823
|
+
this.close();
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
getDateString(date) {
|
|
827
|
+
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
|
828
|
+
}
|
|
829
|
+
isDateInMultiSelection(year, month, day) {
|
|
830
|
+
if (!this.multiDateSelection || this.selectedDates.length === 0)
|
|
831
|
+
return false;
|
|
832
|
+
const cellDate = new Date(year, month, day);
|
|
833
|
+
return this.selectedDates.some(d => this.getDateString(d) === this.getDateString(cellDate));
|
|
834
|
+
}
|
|
835
|
+
apply() {
|
|
836
|
+
// Format minute inputs to 2 digits before applying
|
|
837
|
+
this.formatAllMinuteInputs();
|
|
838
|
+
// Apply time to dates
|
|
839
|
+
if (this.enableTimepicker) {
|
|
840
|
+
if (this.dualCalendar) {
|
|
841
|
+
// Dual calendar with separate start/end times (always 12-hour format)
|
|
842
|
+
if (this.startDate) {
|
|
843
|
+
this.applyTimeToDate(this.startDate, true);
|
|
844
|
+
}
|
|
845
|
+
if (this.endDate) {
|
|
846
|
+
this.applyTimeToDate(this.endDate, false);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
// Single calendar with time (always 12-hour format)
|
|
851
|
+
if (this.startDate) {
|
|
852
|
+
this.applyTimeToDate(this.startDate, true);
|
|
853
|
+
}
|
|
854
|
+
if (this.endDate && !this.singleDatePicker) {
|
|
855
|
+
this.applyTimeToDate(this.endDate, true);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
// Check if the selection matches a predefined range
|
|
860
|
+
this.checkAndSetActiveRange();
|
|
861
|
+
this.emitSelection();
|
|
862
|
+
this.disableHighlight = true;
|
|
863
|
+
this.close();
|
|
864
|
+
}
|
|
865
|
+
cancel() {
|
|
866
|
+
this.startDate = null;
|
|
867
|
+
this.endDate = null;
|
|
868
|
+
this.selectedDates = [];
|
|
869
|
+
this.close();
|
|
870
|
+
}
|
|
871
|
+
clear() {
|
|
872
|
+
this.startDate = null;
|
|
873
|
+
this.endDate = null;
|
|
874
|
+
this.selectedDates = [];
|
|
875
|
+
this.activeRange = null; // Clear active range
|
|
876
|
+
// Reset right calendar to original position (next month after left) when end date is cleared
|
|
877
|
+
if (this.dualCalendar && !this.endDate) {
|
|
878
|
+
this.rightMonth = this.leftMonth + 1;
|
|
879
|
+
this.rightYear = this.leftYear;
|
|
880
|
+
if (this.rightMonth > 11) {
|
|
881
|
+
this.rightMonth = 0;
|
|
882
|
+
this.rightYear++;
|
|
883
|
+
}
|
|
884
|
+
this.generateDualCalendars();
|
|
885
|
+
}
|
|
886
|
+
this.emitSelection();
|
|
887
|
+
}
|
|
888
|
+
chooseRange(key) {
|
|
889
|
+
if (!this.customRanges)
|
|
890
|
+
return;
|
|
891
|
+
// Don't allow selecting "Custom Range" directly - it's only activated when manually selecting dates
|
|
892
|
+
if (key === 'Custom Range')
|
|
893
|
+
return;
|
|
894
|
+
const r = this.customRanges[key];
|
|
895
|
+
if (!r)
|
|
896
|
+
return;
|
|
897
|
+
this.startDate = new Date(r.start);
|
|
898
|
+
this.endDate = new Date(r.end);
|
|
899
|
+
this.selectedDates = [];
|
|
900
|
+
this.activeRange = key; // Set active range
|
|
901
|
+
// Navigate calendars to show the selected date range
|
|
902
|
+
if (this.dualCalendar) {
|
|
903
|
+
// For dual calendar: left always shows start date month
|
|
904
|
+
if (this.startDate) {
|
|
905
|
+
this.leftMonth = this.startDate.getMonth();
|
|
906
|
+
this.leftYear = this.startDate.getFullYear();
|
|
907
|
+
}
|
|
908
|
+
// Right calendar logic
|
|
909
|
+
if (this.endDate && this.startDate) {
|
|
910
|
+
const startMonth = this.startDate.getMonth();
|
|
911
|
+
const startYear = this.startDate.getFullYear();
|
|
912
|
+
const endMonth = this.endDate.getMonth();
|
|
913
|
+
const endYear = this.endDate.getFullYear();
|
|
914
|
+
// Only move right calendar if end date is in a different month than start date
|
|
915
|
+
if (endMonth !== startMonth || endYear !== startYear) {
|
|
916
|
+
this.rightMonth = endMonth;
|
|
917
|
+
this.rightYear = endYear;
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
// If both dates are in same month, reset right calendar to default position (next month after left)
|
|
921
|
+
this.rightMonth = this.leftMonth + 1;
|
|
922
|
+
this.rightYear = this.leftYear;
|
|
923
|
+
if (this.rightMonth > 11) {
|
|
924
|
+
this.rightMonth = 0;
|
|
925
|
+
this.rightYear++;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
else if (this.endDate && !this.startDate) {
|
|
930
|
+
// If only end date exists, show it in right calendar
|
|
931
|
+
this.rightMonth = this.endDate.getMonth();
|
|
932
|
+
this.rightYear = this.endDate.getFullYear();
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
// If no end date, reset right calendar to default position
|
|
936
|
+
this.rightMonth = this.leftMonth + 1;
|
|
937
|
+
this.rightYear = this.leftYear;
|
|
938
|
+
if (this.rightMonth > 11) {
|
|
939
|
+
this.rightMonth = 0;
|
|
940
|
+
this.rightYear++;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
this.generateDualCalendars();
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
// For single calendar: show the start date month (or end date if only end date exists)
|
|
947
|
+
if (this.startDate) {
|
|
948
|
+
this.month = this.startDate.getMonth();
|
|
949
|
+
this.year = this.startDate.getFullYear();
|
|
950
|
+
}
|
|
951
|
+
else if (this.endDate) {
|
|
952
|
+
this.month = this.endDate.getMonth();
|
|
953
|
+
this.year = this.endDate.getFullYear();
|
|
954
|
+
}
|
|
955
|
+
this.generateCalendar();
|
|
956
|
+
}
|
|
957
|
+
this.emitSelection();
|
|
958
|
+
if (this.autoApply || this.closeOnAutoApply) {
|
|
959
|
+
this.close();
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
// emitSelection() {
|
|
963
|
+
// const selection: CalendarSelection = {
|
|
964
|
+
// startDate: this.startDate,
|
|
965
|
+
// endDate: this.endDate
|
|
966
|
+
// };
|
|
967
|
+
// if (this.multiDateSelection) {
|
|
968
|
+
// selection.selectedDates = [...this.selectedDates];
|
|
969
|
+
// }
|
|
970
|
+
// this.selected.emit(selection);
|
|
971
|
+
// }
|
|
972
|
+
emitSelection() {
|
|
973
|
+
const selection = {
|
|
974
|
+
startDate: this.startDate ? this.formatDateToString(this.startDate) : null,
|
|
975
|
+
endDate: this.endDate ? this.formatDateToString(this.endDate) : null,
|
|
976
|
+
};
|
|
977
|
+
if (this.multiDateSelection && this.selectedDates.length > 0) {
|
|
978
|
+
selection.selectedDates = this.selectedDates.map(d => this.formatDateToString(d));
|
|
979
|
+
}
|
|
980
|
+
this.selected.emit(selection);
|
|
981
|
+
}
|
|
982
|
+
addDays(date, days) {
|
|
983
|
+
const d = new Date(date);
|
|
984
|
+
d.setDate(d.getDate() + days);
|
|
985
|
+
return d;
|
|
986
|
+
}
|
|
987
|
+
generateCalendar() {
|
|
988
|
+
this.calendar = this.buildCalendar(this.year, this.month);
|
|
989
|
+
}
|
|
990
|
+
nextMonth() {
|
|
991
|
+
if (!this.dualCalendar) {
|
|
992
|
+
this.month++;
|
|
993
|
+
if (this.month > 11) {
|
|
994
|
+
this.month = 0;
|
|
995
|
+
this.year++;
|
|
996
|
+
}
|
|
997
|
+
this.generateCalendar();
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
// For dual calendar, this should not be used - use nextLeftMonth or nextRightMonth instead
|
|
1001
|
+
this.nextLeftMonth();
|
|
1002
|
+
}
|
|
1003
|
+
prevMonth() {
|
|
1004
|
+
if (!this.dualCalendar) {
|
|
1005
|
+
this.month--;
|
|
1006
|
+
if (this.month < 0) {
|
|
1007
|
+
this.month = 11;
|
|
1008
|
+
this.year--;
|
|
1009
|
+
}
|
|
1010
|
+
this.generateCalendar();
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
// For dual calendar, this should not be used - use prevLeftMonth or prevRightMonth instead
|
|
1014
|
+
this.prevLeftMonth();
|
|
1015
|
+
}
|
|
1016
|
+
// Independent navigation for left calendar
|
|
1017
|
+
nextLeftMonth() {
|
|
1018
|
+
this.leftMonth++;
|
|
1019
|
+
if (this.leftMonth > 11) {
|
|
1020
|
+
this.leftMonth = 0;
|
|
1021
|
+
this.leftYear++;
|
|
1022
|
+
}
|
|
1023
|
+
this.leftCalendar = this.buildCalendar(this.leftYear, this.leftMonth);
|
|
1024
|
+
}
|
|
1025
|
+
prevLeftMonth() {
|
|
1026
|
+
this.leftMonth--;
|
|
1027
|
+
if (this.leftMonth < 0) {
|
|
1028
|
+
this.leftMonth = 11;
|
|
1029
|
+
this.leftYear--;
|
|
1030
|
+
}
|
|
1031
|
+
this.leftCalendar = this.buildCalendar(this.leftYear, this.leftMonth);
|
|
1032
|
+
}
|
|
1033
|
+
// Independent navigation for right calendar
|
|
1034
|
+
nextRightMonth() {
|
|
1035
|
+
this.rightMonth++;
|
|
1036
|
+
if (this.rightMonth > 11) {
|
|
1037
|
+
this.rightMonth = 0;
|
|
1038
|
+
this.rightYear++;
|
|
1039
|
+
}
|
|
1040
|
+
this.rightCalendar = this.buildCalendar(this.rightYear, this.rightMonth);
|
|
1041
|
+
}
|
|
1042
|
+
prevRightMonth() {
|
|
1043
|
+
this.rightMonth--;
|
|
1044
|
+
if (this.rightMonth < 0) {
|
|
1045
|
+
this.rightMonth = 11;
|
|
1046
|
+
this.rightYear--;
|
|
1047
|
+
}
|
|
1048
|
+
this.rightCalendar = this.buildCalendar(this.rightYear, this.rightMonth);
|
|
1049
|
+
}
|
|
1050
|
+
initializeDual() {
|
|
1051
|
+
this.leftMonth = this.today.getMonth();
|
|
1052
|
+
this.leftYear = this.today.getFullYear();
|
|
1053
|
+
// Initialize right calendar to next month, but they can move independently
|
|
1054
|
+
this.rightMonth = this.leftMonth + 1;
|
|
1055
|
+
this.rightYear = this.leftYear;
|
|
1056
|
+
if (this.rightMonth > 11) {
|
|
1057
|
+
this.rightMonth = 0;
|
|
1058
|
+
this.rightYear++;
|
|
1059
|
+
}
|
|
1060
|
+
this.generateDualCalendars();
|
|
1061
|
+
}
|
|
1062
|
+
generateDualCalendars() {
|
|
1063
|
+
this.leftCalendar = this.buildCalendar(this.leftYear, this.leftMonth);
|
|
1064
|
+
this.rightCalendar = this.buildCalendar(this.rightYear, this.rightMonth);
|
|
1065
|
+
}
|
|
1066
|
+
buildCalendar(year, month) {
|
|
1067
|
+
const firstDay = new Date(year, month, 1).getDay();
|
|
1068
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
1069
|
+
const prevMonthDays = new Date(year, month, 0).getDate();
|
|
1070
|
+
const grid = [];
|
|
1071
|
+
let row = [];
|
|
1072
|
+
// Adjust first day (0 = Sunday, 1 = Monday, etc.)
|
|
1073
|
+
const adjustedFirstDay = firstDay === 0 ? 6 : firstDay - 1; // Make Monday = 0
|
|
1074
|
+
for (let i = adjustedFirstDay - 1; i >= 0; i--) {
|
|
1075
|
+
row.push({ day: prevMonthDays - i, currentMonth: false });
|
|
1076
|
+
}
|
|
1077
|
+
for (let d = 1; d <= daysInMonth; d++) {
|
|
1078
|
+
row.push({ day: d, currentMonth: true });
|
|
1079
|
+
if (row.length === 7) {
|
|
1080
|
+
grid.push(row);
|
|
1081
|
+
row = [];
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
let nextMonthDay = 1;
|
|
1085
|
+
while (row.length > 0 && row.length < 7) {
|
|
1086
|
+
row.push({ day: nextMonthDay++, currentMonth: false });
|
|
1087
|
+
}
|
|
1088
|
+
if (row.length)
|
|
1089
|
+
grid.push(row);
|
|
1090
|
+
// Ensure we always have 6 rows (42 cells total) for consistent layout
|
|
1091
|
+
while (grid.length < 6) {
|
|
1092
|
+
const newRow = [];
|
|
1093
|
+
for (let i = 0; i < 7; i++) {
|
|
1094
|
+
newRow.push({ day: nextMonthDay++, currentMonth: false });
|
|
1095
|
+
}
|
|
1096
|
+
grid.push(newRow);
|
|
1097
|
+
}
|
|
1098
|
+
return grid;
|
|
1099
|
+
}
|
|
1100
|
+
isDateSelected(year, month, day) {
|
|
1101
|
+
if (this.disableHighlight)
|
|
1102
|
+
return false;
|
|
1103
|
+
if (!day)
|
|
1104
|
+
return false;
|
|
1105
|
+
// Multi-date selection
|
|
1106
|
+
if (this.multiDateSelection) {
|
|
1107
|
+
return this.isDateInMultiSelection(year, month, day);
|
|
1108
|
+
}
|
|
1109
|
+
const cellDate = new Date(year, month, day);
|
|
1110
|
+
// Check if it's today (highlight today by default if no date selected)
|
|
1111
|
+
const today = new Date();
|
|
1112
|
+
const isToday = cellDate.getFullYear() === today.getFullYear() &&
|
|
1113
|
+
cellDate.getMonth() === today.getMonth() &&
|
|
1114
|
+
cellDate.getDate() === today.getDate();
|
|
1115
|
+
// If no startDate is set and it's today, highlight it
|
|
1116
|
+
if (!this.startDate && isToday) {
|
|
1117
|
+
return true;
|
|
1118
|
+
}
|
|
1119
|
+
if (!this.startDate)
|
|
1120
|
+
return false;
|
|
1121
|
+
// Check if date is disabled
|
|
1122
|
+
if (this.minDate && cellDate < this.minDate)
|
|
1123
|
+
return false;
|
|
1124
|
+
if (this.maxDate && cellDate > this.maxDate)
|
|
1125
|
+
return false;
|
|
1126
|
+
const sameDay = cellDate.getFullYear() === this.startDate.getFullYear() &&
|
|
1127
|
+
cellDate.getMonth() === this.startDate.getMonth() &&
|
|
1128
|
+
cellDate.getDate() === this.startDate.getDate();
|
|
1129
|
+
if (this.singleDatePicker)
|
|
1130
|
+
return sameDay;
|
|
1131
|
+
// For range selection: only highlight start and end dates (not in-between)
|
|
1132
|
+
if (this.startDate && this.endDate) {
|
|
1133
|
+
const start = new Date(this.startDate.getFullYear(), this.startDate.getMonth(), this.startDate.getDate());
|
|
1134
|
+
const end = new Date(this.endDate.getFullYear(), this.endDate.getMonth(), this.endDate.getDate());
|
|
1135
|
+
return cellDate.getTime() === start.getTime() || cellDate.getTime() === end.getTime();
|
|
1136
|
+
}
|
|
1137
|
+
// If only start date is selected and hovering, check if this is start or hovered end
|
|
1138
|
+
if (this.startDate && !this.endDate && this.hoveredDate) {
|
|
1139
|
+
const start = new Date(this.startDate.getFullYear(), this.startDate.getMonth(), this.startDate.getDate());
|
|
1140
|
+
const hovered = new Date(this.hoveredDate.getFullYear(), this.hoveredDate.getMonth(), this.hoveredDate.getDate());
|
|
1141
|
+
// Show both start and hovered date as selected (circular black)
|
|
1142
|
+
return cellDate.getTime() === start.getTime() || cellDate.getTime() === hovered.getTime();
|
|
1143
|
+
}
|
|
1144
|
+
return sameDay;
|
|
1145
|
+
}
|
|
1146
|
+
isDateInRange(year, month, day) {
|
|
1147
|
+
if (this.disableHighlight || !day)
|
|
1148
|
+
return false;
|
|
1149
|
+
if (this.singleDatePicker)
|
|
1150
|
+
return false;
|
|
1151
|
+
if (this.multiDateSelection)
|
|
1152
|
+
return false;
|
|
1153
|
+
const cellDate = new Date(year, month, day);
|
|
1154
|
+
// If both start and end are selected, show gray background for dates in between
|
|
1155
|
+
if (this.startDate && this.endDate) {
|
|
1156
|
+
const start = new Date(this.startDate.getFullYear(), this.startDate.getMonth(), this.startDate.getDate());
|
|
1157
|
+
const end = new Date(this.endDate.getFullYear(), this.endDate.getMonth(), this.endDate.getDate());
|
|
1158
|
+
return cellDate > start && cellDate < end;
|
|
1159
|
+
}
|
|
1160
|
+
// If only start is selected and hovering, show preview range
|
|
1161
|
+
if (this.startDate && !this.endDate && this.hoveredDate) {
|
|
1162
|
+
const start = new Date(this.startDate.getFullYear(), this.startDate.getMonth(), this.startDate.getDate());
|
|
1163
|
+
const hovered = new Date(this.hoveredDate.getFullYear(), this.hoveredDate.getMonth(), this.hoveredDate.getDate());
|
|
1164
|
+
// Determine which is earlier - show gray background for dates between them
|
|
1165
|
+
const minDate = hovered < start ? hovered : start;
|
|
1166
|
+
const maxDate = hovered >= start ? hovered : start;
|
|
1167
|
+
return cellDate > minDate && cellDate < maxDate;
|
|
1168
|
+
}
|
|
1169
|
+
return false;
|
|
1170
|
+
}
|
|
1171
|
+
isDateDisabled(year, month, day) {
|
|
1172
|
+
if (!day)
|
|
1173
|
+
return false;
|
|
1174
|
+
const cellDate = new Date(year, month, day);
|
|
1175
|
+
if (this.minDate && cellDate < this.minDate)
|
|
1176
|
+
return true;
|
|
1177
|
+
if (this.maxDate && cellDate > this.maxDate)
|
|
1178
|
+
return true;
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
isToday(year, month, day) {
|
|
1182
|
+
if (!day)
|
|
1183
|
+
return false;
|
|
1184
|
+
const today = new Date();
|
|
1185
|
+
const cellDate = new Date(year, month, day);
|
|
1186
|
+
return cellDate.getFullYear() === today.getFullYear() &&
|
|
1187
|
+
cellDate.getMonth() === today.getMonth() &&
|
|
1188
|
+
cellDate.getDate() === today.getDate();
|
|
1189
|
+
}
|
|
1190
|
+
getDisplayValue() {
|
|
1191
|
+
if (this.multiDateSelection && this.selectedDates.length > 0) {
|
|
1192
|
+
if (this.selectedDates.length === 1) {
|
|
1193
|
+
return moment(this.selectedDates[0]).format(this.displayFormat);
|
|
1194
|
+
}
|
|
1195
|
+
return `${this.selectedDates.length} dates selected`;
|
|
1196
|
+
}
|
|
1197
|
+
if (!this.startDate)
|
|
1198
|
+
return '';
|
|
1199
|
+
// Prefer moment formatting for consistent display
|
|
1200
|
+
let dateStr = moment(this.startDate).format(this.displayFormat);
|
|
1201
|
+
if (this.enableTimepicker && !this.dualCalendar) {
|
|
1202
|
+
const hr = this.startDate.getHours().toString().padStart(2, '0');
|
|
1203
|
+
const min = this.startDate.getMinutes().toString().padStart(2, '0');
|
|
1204
|
+
dateStr += ` ${hr}:${min}`;
|
|
1205
|
+
if (this.enableSeconds) {
|
|
1206
|
+
const sec = this.startDate.getSeconds().toString().padStart(2, '0');
|
|
1207
|
+
dateStr += `:${sec}`;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
if (this.endDate && !this.singleDatePicker) {
|
|
1211
|
+
let endStr = moment(this.endDate).format(this.displayFormat);
|
|
1212
|
+
if (this.enableTimepicker) {
|
|
1213
|
+
if (this.dualCalendar) {
|
|
1214
|
+
const startHr = this.startDate.getHours().toString().padStart(2, '0');
|
|
1215
|
+
const startMin = this.startDate.getMinutes().toString().padStart(2, '0');
|
|
1216
|
+
dateStr += ` ${startHr}:${startMin}`;
|
|
1217
|
+
if (this.enableSeconds) {
|
|
1218
|
+
const startSec = this.startDate.getSeconds().toString().padStart(2, '0');
|
|
1219
|
+
dateStr += `:${startSec}`;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
const endHr = this.endDate.getHours().toString().padStart(2, '0');
|
|
1223
|
+
const endMin = this.endDate.getMinutes().toString().padStart(2, '0');
|
|
1224
|
+
endStr += ` ${endHr}:${endMin}`;
|
|
1225
|
+
if (this.enableSeconds) {
|
|
1226
|
+
const endSec = this.endDate.getSeconds().toString().padStart(2, '0');
|
|
1227
|
+
endStr += `:${endSec}`;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return `${dateStr} - ${endStr}`;
|
|
1231
|
+
}
|
|
1232
|
+
return dateStr;
|
|
1233
|
+
}
|
|
1234
|
+
// Time picker helpers
|
|
1235
|
+
getTimeInputValue(isStart = true) {
|
|
1236
|
+
const h = isStart ? this.startHour : this.endHour;
|
|
1237
|
+
const m = isStart ? this.startMinute : this.endMinute;
|
|
1238
|
+
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
|
|
1239
|
+
}
|
|
1240
|
+
getSingleTimeInputValue() {
|
|
1241
|
+
return `${this.selectedHour.toString().padStart(2, '0')}:${this.selectedMinute.toString().padStart(2, '0')}`;
|
|
1242
|
+
}
|
|
1243
|
+
// NEW: Helper to build display value for BkTimePicker (single calendar)
|
|
1244
|
+
getSingleTimePickerDisplay() {
|
|
1245
|
+
const hour = this.selectedHour || 12;
|
|
1246
|
+
const minuteStr = this.selectedMinute.toString().padStart(2, '0');
|
|
1247
|
+
const ampm = this.selectedAMPM || 'AM';
|
|
1248
|
+
return `${hour}:${minuteStr} ${ampm}`;
|
|
1249
|
+
}
|
|
1250
|
+
// NEW: Helper to build display value for BkTimePicker (dual calendar)
|
|
1251
|
+
getDualTimePickerDisplay(isStart = true) {
|
|
1252
|
+
const hour = isStart ? (this.startHour || 12) : (this.endHour || 12);
|
|
1253
|
+
const minute = isStart ? this.startMinute : this.endMinute;
|
|
1254
|
+
const ampm = isStart ? (this.startAMPM || 'AM') : (this.endAMPM || 'AM');
|
|
1255
|
+
const minuteStr = minute.toString().padStart(2, '0');
|
|
1256
|
+
return `${hour}:${minuteStr} ${ampm}`;
|
|
1257
|
+
}
|
|
1258
|
+
// Coordination helpers for embedded BkTimePicker instances
|
|
1259
|
+
onTimePickerOpened(pickerId) {
|
|
1260
|
+
// Close previously open picker inside this calendar
|
|
1261
|
+
if (this.openTimePickerId && this.openTimePickerId !== pickerId) {
|
|
1262
|
+
if (!this.closePickerCounter[this.openTimePickerId]) {
|
|
1263
|
+
this.closePickerCounter[this.openTimePickerId] = 0;
|
|
1264
|
+
}
|
|
1265
|
+
this.closePickerCounter[this.openTimePickerId]++;
|
|
1266
|
+
}
|
|
1267
|
+
this.openTimePickerId = pickerId;
|
|
1268
|
+
}
|
|
1269
|
+
onTimePickerClosed(pickerId) {
|
|
1270
|
+
if (this.openTimePickerId === pickerId) {
|
|
1271
|
+
this.openTimePickerId = null;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
shouldClosePicker(pickerId) {
|
|
1275
|
+
return this.closePickerCounter[pickerId] || 0;
|
|
1276
|
+
}
|
|
1277
|
+
// NEW: Parse "H:MM AM/PM" (or "HH:MM" 24h) from BkTimePicker
|
|
1278
|
+
parsePickerTimeString(timeStr) {
|
|
1279
|
+
if (!timeStr) {
|
|
1280
|
+
return { hour12: 12, minute: 0, ampm: 'AM' };
|
|
1281
|
+
}
|
|
1282
|
+
const parts = timeStr.trim().split(' ');
|
|
1283
|
+
const timePart = parts[0] || '12:00';
|
|
1284
|
+
let ampmPart = (parts[1] || '').toUpperCase();
|
|
1285
|
+
const [hourStr, minuteStr] = timePart.split(':');
|
|
1286
|
+
let hour = parseInt(hourStr || '12', 10);
|
|
1287
|
+
const minute = parseInt(minuteStr || '0', 10);
|
|
1288
|
+
if (ampmPart !== 'AM' && ampmPart !== 'PM') {
|
|
1289
|
+
// Interpret as 24-hour input and convert
|
|
1290
|
+
if (hour >= 12) {
|
|
1291
|
+
ampmPart = 'PM';
|
|
1292
|
+
if (hour > 12)
|
|
1293
|
+
hour -= 12;
|
|
1294
|
+
}
|
|
1295
|
+
else {
|
|
1296
|
+
ampmPart = 'AM';
|
|
1297
|
+
if (hour === 0)
|
|
1298
|
+
hour = 12;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
// Clamp to 1-12 range just in case
|
|
1302
|
+
if (hour < 1)
|
|
1303
|
+
hour = 1;
|
|
1304
|
+
if (hour > 12)
|
|
1305
|
+
hour = 12;
|
|
1306
|
+
return { hour12: hour, minute, ampm: ampmPart };
|
|
1307
|
+
}
|
|
1308
|
+
// NEW: Handle BkTimePicker change for single calendar
|
|
1309
|
+
onSingleTimePickerChange(time) {
|
|
1310
|
+
const { hour12, minute, ampm } = this.parsePickerTimeString(time);
|
|
1311
|
+
this.selectedHour = hour12;
|
|
1312
|
+
this.selectedMinute = minute;
|
|
1313
|
+
this.selectedAMPM = ampm;
|
|
1314
|
+
if (this.startDate) {
|
|
1315
|
+
let h24 = hour12;
|
|
1316
|
+
if (ampm === 'PM' && h24 < 12)
|
|
1317
|
+
h24 += 12;
|
|
1318
|
+
if (ampm === 'AM' && h24 === 12)
|
|
1319
|
+
h24 = 0;
|
|
1320
|
+
this.startDate.setHours(h24, minute, this.selectedSecond);
|
|
1321
|
+
this.emitSelection();
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
// NEW: Handle BkTimePicker change for dual calendar
|
|
1325
|
+
onDualTimePickerChange(time, isStart = true) {
|
|
1326
|
+
const { hour12, minute, ampm } = this.parsePickerTimeString(time);
|
|
1327
|
+
if (isStart) {
|
|
1328
|
+
this.startHour = hour12;
|
|
1329
|
+
this.startMinute = minute;
|
|
1330
|
+
this.startAMPM = ampm;
|
|
1331
|
+
if (this.startDate) {
|
|
1332
|
+
let h24 = hour12;
|
|
1333
|
+
if (ampm === 'PM' && h24 < 12)
|
|
1334
|
+
h24 += 12;
|
|
1335
|
+
if (ampm === 'AM' && h24 === 12)
|
|
1336
|
+
h24 = 0;
|
|
1337
|
+
this.startDate.setHours(h24, minute, this.startSecond);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
else {
|
|
1341
|
+
this.endHour = hour12;
|
|
1342
|
+
this.endMinute = minute;
|
|
1343
|
+
this.endAMPM = ampm;
|
|
1344
|
+
if (this.endDate) {
|
|
1345
|
+
let h24 = hour12;
|
|
1346
|
+
if (ampm === 'PM' && h24 < 12)
|
|
1347
|
+
h24 += 12;
|
|
1348
|
+
if (ampm === 'AM' && h24 === 12)
|
|
1349
|
+
h24 = 0;
|
|
1350
|
+
this.endDate.setHours(h24, minute, this.endSecond);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
this.emitSelection();
|
|
1354
|
+
}
|
|
1355
|
+
onTimeChange(event, isStart = true) {
|
|
1356
|
+
const [h, m] = event.target.value.split(':').map(Number);
|
|
1357
|
+
if (isStart) {
|
|
1358
|
+
this.startHour = h;
|
|
1359
|
+
this.startMinute = m;
|
|
1360
|
+
if (this.startDate) {
|
|
1361
|
+
this.startDate.setHours(h, m, this.startSecond);
|
|
1362
|
+
this.emitSelection();
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
this.endHour = h;
|
|
1367
|
+
this.endMinute = m;
|
|
1368
|
+
if (this.endDate) {
|
|
1369
|
+
this.endDate.setHours(h, m, this.endSecond);
|
|
1370
|
+
this.emitSelection();
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
onSingleTimeChange(event) {
|
|
1375
|
+
const [h, m] = event.target.value.split(':').map(Number);
|
|
1376
|
+
this.selectedHour = h;
|
|
1377
|
+
this.selectedMinute = m;
|
|
1378
|
+
if (this.startDate) {
|
|
1379
|
+
this.startDate.setHours(h, m, this.selectedSecond);
|
|
1380
|
+
this.emitSelection();
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
// Custom time picker controls
|
|
1384
|
+
incrementHour(isStart = true) {
|
|
1385
|
+
// 12-hour format: 1-12
|
|
1386
|
+
if (isStart) {
|
|
1387
|
+
this.startHour = this.startHour >= 12 ? 1 : this.startHour + 1;
|
|
1388
|
+
// Toggle AM/PM at 12
|
|
1389
|
+
if (this.startHour === 12) {
|
|
1390
|
+
this.startAMPM = this.startAMPM === 'AM' ? 'PM' : 'AM';
|
|
1391
|
+
}
|
|
1392
|
+
if (this.startDate) {
|
|
1393
|
+
let h = this.startHour;
|
|
1394
|
+
if (this.startAMPM === 'PM' && h < 12)
|
|
1395
|
+
h += 12;
|
|
1396
|
+
if (this.startAMPM === 'AM' && h === 12)
|
|
1397
|
+
h = 0;
|
|
1398
|
+
this.startDate.setHours(h, this.startMinute, this.startSecond);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
else {
|
|
1402
|
+
this.endHour = this.endHour >= 12 ? 1 : this.endHour + 1;
|
|
1403
|
+
// Toggle AM/PM at 12
|
|
1404
|
+
if (this.endHour === 12) {
|
|
1405
|
+
this.endAMPM = this.endAMPM === 'AM' ? 'PM' : 'AM';
|
|
1406
|
+
}
|
|
1407
|
+
if (this.endDate) {
|
|
1408
|
+
let h = this.endHour;
|
|
1409
|
+
if (this.endAMPM === 'PM' && h < 12)
|
|
1410
|
+
h += 12;
|
|
1411
|
+
if (this.endAMPM === 'AM' && h === 12)
|
|
1412
|
+
h = 0;
|
|
1413
|
+
this.endDate.setHours(h, this.endMinute, this.endSecond);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
this.emitSelection();
|
|
1417
|
+
}
|
|
1418
|
+
decrementHour(isStart = true) {
|
|
1419
|
+
// 12-hour format: 1-12
|
|
1420
|
+
if (isStart) {
|
|
1421
|
+
this.startHour = this.startHour <= 1 ? 12 : this.startHour - 1;
|
|
1422
|
+
// Toggle AM/PM at 12
|
|
1423
|
+
if (this.startHour === 12) {
|
|
1424
|
+
this.startAMPM = this.startAMPM === 'AM' ? 'PM' : 'AM';
|
|
1425
|
+
}
|
|
1426
|
+
if (this.startDate) {
|
|
1427
|
+
let h = this.startHour;
|
|
1428
|
+
if (this.startAMPM === 'PM' && h < 12)
|
|
1429
|
+
h += 12;
|
|
1430
|
+
if (this.startAMPM === 'AM' && h === 12)
|
|
1431
|
+
h = 0;
|
|
1432
|
+
this.startDate.setHours(h, this.startMinute, this.startSecond);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
else {
|
|
1436
|
+
this.endHour = this.endHour <= 1 ? 12 : this.endHour - 1;
|
|
1437
|
+
// Toggle AM/PM at 12
|
|
1438
|
+
if (this.endHour === 12) {
|
|
1439
|
+
this.endAMPM = this.endAMPM === 'AM' ? 'PM' : 'AM';
|
|
1440
|
+
}
|
|
1441
|
+
if (this.endDate) {
|
|
1442
|
+
let h = this.endHour;
|
|
1443
|
+
if (this.endAMPM === 'PM' && h < 12)
|
|
1444
|
+
h += 12;
|
|
1445
|
+
if (this.endAMPM === 'AM' && h === 12)
|
|
1446
|
+
h = 0;
|
|
1447
|
+
this.endDate.setHours(h, this.endMinute, this.endSecond);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
this.emitSelection();
|
|
1451
|
+
}
|
|
1452
|
+
incrementMinute(isStart = true) {
|
|
1453
|
+
if (isStart) {
|
|
1454
|
+
this.startMinute = (this.startMinute + 1) % 60;
|
|
1455
|
+
if (this.startDate) {
|
|
1456
|
+
let h = this.startHour;
|
|
1457
|
+
if (this.startAMPM === 'PM' && h < 12)
|
|
1458
|
+
h += 12;
|
|
1459
|
+
if (this.startAMPM === 'AM' && h === 12)
|
|
1460
|
+
h = 0;
|
|
1461
|
+
this.startDate.setHours(h, this.startMinute, this.startSecond);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
else {
|
|
1465
|
+
this.endMinute = (this.endMinute + 1) % 60;
|
|
1466
|
+
if (this.endDate) {
|
|
1467
|
+
let h = this.endHour;
|
|
1468
|
+
if (this.endAMPM === 'PM' && h < 12)
|
|
1469
|
+
h += 12;
|
|
1470
|
+
if (this.endAMPM === 'AM' && h === 12)
|
|
1471
|
+
h = 0;
|
|
1472
|
+
this.endDate.setHours(h, this.endMinute, this.endSecond);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
this.emitSelection();
|
|
1476
|
+
}
|
|
1477
|
+
decrementMinute(isStart = true) {
|
|
1478
|
+
if (isStart) {
|
|
1479
|
+
this.startMinute = this.startMinute <= 0 ? 59 : this.startMinute - 1;
|
|
1480
|
+
if (this.startDate) {
|
|
1481
|
+
let h = this.startHour;
|
|
1482
|
+
if (this.startAMPM === 'PM' && h < 12)
|
|
1483
|
+
h += 12;
|
|
1484
|
+
if (this.startAMPM === 'AM' && h === 12)
|
|
1485
|
+
h = 0;
|
|
1486
|
+
this.startDate.setHours(h, this.startMinute, this.startSecond);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
else {
|
|
1490
|
+
this.endMinute = this.endMinute <= 0 ? 59 : this.endMinute - 1;
|
|
1491
|
+
if (this.endDate) {
|
|
1492
|
+
let h = this.endHour;
|
|
1493
|
+
if (this.endAMPM === 'PM' && h < 12)
|
|
1494
|
+
h += 12;
|
|
1495
|
+
if (this.endAMPM === 'AM' && h === 12)
|
|
1496
|
+
h = 0;
|
|
1497
|
+
this.endDate.setHours(h, this.endMinute, this.endSecond);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
this.emitSelection();
|
|
1501
|
+
}
|
|
1502
|
+
toggleAMPM(isStart = true) {
|
|
1503
|
+
if (isStart) {
|
|
1504
|
+
this.startAMPM = this.startAMPM === 'AM' ? 'PM' : 'AM';
|
|
1505
|
+
if (this.startDate) {
|
|
1506
|
+
let h = this.startHour;
|
|
1507
|
+
if (this.startAMPM === 'PM' && h < 12)
|
|
1508
|
+
h += 12;
|
|
1509
|
+
if (this.startAMPM === 'AM' && h === 12)
|
|
1510
|
+
h = 0;
|
|
1511
|
+
this.startDate.setHours(h, this.startMinute, this.startSecond);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
else {
|
|
1515
|
+
this.endAMPM = this.endAMPM === 'AM' ? 'PM' : 'AM';
|
|
1516
|
+
if (this.endDate) {
|
|
1517
|
+
let h = this.endHour;
|
|
1518
|
+
if (this.endAMPM === 'PM' && h < 12)
|
|
1519
|
+
h += 12;
|
|
1520
|
+
if (this.endAMPM === 'AM' && h === 12)
|
|
1521
|
+
h = 0;
|
|
1522
|
+
this.endDate.setHours(h, this.endMinute, this.endSecond);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
this.emitSelection();
|
|
1526
|
+
}
|
|
1527
|
+
// Single calendar time picker controls (12-hour format: 1-12)
|
|
1528
|
+
incrementSingleHour() {
|
|
1529
|
+
this.selectedHour = this.selectedHour >= 12 ? 1 : this.selectedHour + 1;
|
|
1530
|
+
// Toggle AM/PM at 12
|
|
1531
|
+
if (this.selectedHour === 12) {
|
|
1532
|
+
this.selectedAMPM = this.selectedAMPM === 'AM' ? 'PM' : 'AM';
|
|
1533
|
+
}
|
|
1534
|
+
if (this.startDate) {
|
|
1535
|
+
let h = this.selectedHour;
|
|
1536
|
+
if (this.selectedAMPM === 'PM' && h < 12)
|
|
1537
|
+
h += 12;
|
|
1538
|
+
if (this.selectedAMPM === 'AM' && h === 12)
|
|
1539
|
+
h = 0;
|
|
1540
|
+
this.startDate.setHours(h, this.selectedMinute, this.selectedSecond);
|
|
1541
|
+
this.emitSelection();
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
decrementSingleHour() {
|
|
1545
|
+
this.selectedHour = this.selectedHour <= 1 ? 12 : this.selectedHour - 1;
|
|
1546
|
+
// Toggle AM/PM at 12
|
|
1547
|
+
if (this.selectedHour === 12) {
|
|
1548
|
+
this.selectedAMPM = this.selectedAMPM === 'AM' ? 'PM' : 'AM';
|
|
1549
|
+
}
|
|
1550
|
+
if (this.startDate) {
|
|
1551
|
+
let h = this.selectedHour;
|
|
1552
|
+
if (this.selectedAMPM === 'PM' && h < 12)
|
|
1553
|
+
h += 12;
|
|
1554
|
+
if (this.selectedAMPM === 'AM' && h === 12)
|
|
1555
|
+
h = 0;
|
|
1556
|
+
this.startDate.setHours(h, this.selectedMinute, this.selectedSecond);
|
|
1557
|
+
this.emitSelection();
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
incrementSingleMinute() {
|
|
1561
|
+
this.selectedMinute = (this.selectedMinute + 1) % 60;
|
|
1562
|
+
if (this.startDate) {
|
|
1563
|
+
let h = this.selectedHour;
|
|
1564
|
+
if (this.selectedAMPM === 'PM' && h < 12)
|
|
1565
|
+
h += 12;
|
|
1566
|
+
if (this.selectedAMPM === 'AM' && h === 12)
|
|
1567
|
+
h = 0;
|
|
1568
|
+
this.startDate.setHours(h, this.selectedMinute, this.selectedSecond);
|
|
1569
|
+
this.emitSelection();
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
decrementSingleMinute() {
|
|
1573
|
+
this.selectedMinute = this.selectedMinute <= 0 ? 59 : this.selectedMinute - 1;
|
|
1574
|
+
if (this.startDate) {
|
|
1575
|
+
let h = this.selectedHour;
|
|
1576
|
+
if (this.selectedAMPM === 'PM' && h < 12)
|
|
1577
|
+
h += 12;
|
|
1578
|
+
if (this.selectedAMPM === 'AM' && h === 12)
|
|
1579
|
+
h = 0;
|
|
1580
|
+
this.startDate.setHours(h, this.selectedMinute, this.selectedSecond);
|
|
1581
|
+
this.emitSelection();
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
toggleSingleAMPM() {
|
|
1585
|
+
this.selectedAMPM = this.selectedAMPM === 'AM' ? 'PM' : 'AM';
|
|
1586
|
+
if (this.startDate) {
|
|
1587
|
+
let h = this.selectedHour;
|
|
1588
|
+
if (this.selectedAMPM === 'PM' && h < 12)
|
|
1589
|
+
h += 12;
|
|
1590
|
+
if (this.selectedAMPM === 'AM' && h === 12)
|
|
1591
|
+
h = 0;
|
|
1592
|
+
this.startDate.setHours(h, this.selectedMinute, this.selectedSecond);
|
|
1593
|
+
this.emitSelection();
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
getMonthName(month) {
|
|
1597
|
+
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
1598
|
+
return months[month];
|
|
1599
|
+
}
|
|
1600
|
+
// Input handlers for direct hour/minute input (12-hour format only)
|
|
1601
|
+
onHourInput(event, isStart = true, isSingle = false) {
|
|
1602
|
+
const inputValue = event.target.value;
|
|
1603
|
+
// Allow empty input while typing
|
|
1604
|
+
if (inputValue === '' || inputValue === null || inputValue === undefined) {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
let value = parseInt(inputValue) || 0;
|
|
1608
|
+
// Validate: 1-12 for 12-hour format
|
|
1609
|
+
if (value < 1)
|
|
1610
|
+
value = 1;
|
|
1611
|
+
if (value > 12)
|
|
1612
|
+
value = 12;
|
|
1613
|
+
if (isSingle) {
|
|
1614
|
+
this.selectedHour = value;
|
|
1615
|
+
if (this.startDate) {
|
|
1616
|
+
let h = value;
|
|
1617
|
+
if (this.selectedAMPM === 'PM' && h < 12)
|
|
1618
|
+
h += 12;
|
|
1619
|
+
if (this.selectedAMPM === 'AM' && h === 12)
|
|
1620
|
+
h = 0;
|
|
1621
|
+
this.startDate.setHours(h, this.selectedMinute, this.selectedSecond);
|
|
1622
|
+
this.emitSelection();
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
else if (isStart) {
|
|
1626
|
+
this.startHour = value;
|
|
1627
|
+
if (this.startDate) {
|
|
1628
|
+
let h = value;
|
|
1629
|
+
if (this.startAMPM === 'PM' && h < 12)
|
|
1630
|
+
h += 12;
|
|
1631
|
+
if (this.startAMPM === 'AM' && h === 12)
|
|
1632
|
+
h = 0;
|
|
1633
|
+
this.startDate.setHours(h, this.startMinute, this.startSecond);
|
|
1634
|
+
this.emitSelection();
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
else {
|
|
1638
|
+
this.endHour = value;
|
|
1639
|
+
if (this.endDate) {
|
|
1640
|
+
let h = value;
|
|
1641
|
+
if (this.endAMPM === 'PM' && h < 12)
|
|
1642
|
+
h += 12;
|
|
1643
|
+
if (this.endAMPM === 'AM' && h === 12)
|
|
1644
|
+
h = 0;
|
|
1645
|
+
this.endDate.setHours(h, this.endMinute, this.endSecond);
|
|
1646
|
+
this.emitSelection();
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
// Don't format during input, only on blur
|
|
1650
|
+
event.target.value = value.toString();
|
|
1651
|
+
}
|
|
1652
|
+
onHourBlur(event, isStart = true, isSingle = false) {
|
|
1653
|
+
const inputValue = event.target.value;
|
|
1654
|
+
if (inputValue === '' || inputValue === null || inputValue === undefined) {
|
|
1655
|
+
// If empty, set to current value
|
|
1656
|
+
const currentValue = isSingle ? this.selectedHour : (isStart ? this.startHour : this.endHour);
|
|
1657
|
+
event.target.value = currentValue.toString();
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
let value = parseInt(inputValue) || 0;
|
|
1661
|
+
if (value < 1)
|
|
1662
|
+
value = 1;
|
|
1663
|
+
if (value > 12)
|
|
1664
|
+
value = 12;
|
|
1665
|
+
// Format to single digit (no padding for hours in 12-hour format)
|
|
1666
|
+
event.target.value = value.toString();
|
|
1667
|
+
// Update the value
|
|
1668
|
+
if (isSingle) {
|
|
1669
|
+
this.selectedHour = value;
|
|
1670
|
+
if (this.startDate) {
|
|
1671
|
+
let h = value;
|
|
1672
|
+
if (this.selectedAMPM === 'PM' && h < 12)
|
|
1673
|
+
h += 12;
|
|
1674
|
+
if (this.selectedAMPM === 'AM' && h === 12)
|
|
1675
|
+
h = 0;
|
|
1676
|
+
this.startDate.setHours(h, this.selectedMinute, this.selectedSecond);
|
|
1677
|
+
this.emitSelection();
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
else if (isStart) {
|
|
1681
|
+
this.startHour = value;
|
|
1682
|
+
if (this.startDate) {
|
|
1683
|
+
let h = value;
|
|
1684
|
+
if (this.startAMPM === 'PM' && h < 12)
|
|
1685
|
+
h += 12;
|
|
1686
|
+
if (this.startAMPM === 'AM' && h === 12)
|
|
1687
|
+
h = 0;
|
|
1688
|
+
this.startDate.setHours(h, this.startMinute, this.startSecond);
|
|
1689
|
+
this.emitSelection();
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
else {
|
|
1693
|
+
this.endHour = value;
|
|
1694
|
+
if (this.endDate) {
|
|
1695
|
+
let h = value;
|
|
1696
|
+
if (this.endAMPM === 'PM' && h < 12)
|
|
1697
|
+
h += 12;
|
|
1698
|
+
if (this.endAMPM === 'AM' && h === 12)
|
|
1699
|
+
h = 0;
|
|
1700
|
+
this.endDate.setHours(h, this.endMinute, this.endSecond);
|
|
1701
|
+
this.emitSelection();
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
onMinuteInput(event, isStart = true, isSingle = false) {
|
|
1706
|
+
const inputValue = event.target.value;
|
|
1707
|
+
const key = `${isStart ? 'start' : 'end'}_${isSingle ? 'single' : 'dual'}`;
|
|
1708
|
+
// Remove any non-digit characters
|
|
1709
|
+
const digitsOnly = inputValue.replace(/\D/g, '');
|
|
1710
|
+
// Store raw input value for display
|
|
1711
|
+
this.minuteInputValues[key] = digitsOnly;
|
|
1712
|
+
// Allow empty input while typing
|
|
1713
|
+
if (digitsOnly === '' || digitsOnly === null || digitsOnly === undefined) {
|
|
1714
|
+
return; // Don't modify the input, let user clear it
|
|
1715
|
+
}
|
|
1716
|
+
// Allow typing up to 2 digits without formatting
|
|
1717
|
+
let value = parseInt(digitsOnly) || 0;
|
|
1718
|
+
// If user types more than 2 digits, take only first 2
|
|
1719
|
+
if (digitsOnly.length > 2) {
|
|
1720
|
+
value = parseInt(digitsOnly.substring(0, 2));
|
|
1721
|
+
this.minuteInputValues[key] = digitsOnly.substring(0, 2);
|
|
1722
|
+
event.target.value = digitsOnly.substring(0, 2);
|
|
1723
|
+
}
|
|
1724
|
+
// If value exceeds 59, clamp it to 59
|
|
1725
|
+
if (value > 59) {
|
|
1726
|
+
value = 59;
|
|
1727
|
+
this.minuteInputValues[key] = '59';
|
|
1728
|
+
event.target.value = '59';
|
|
1729
|
+
}
|
|
1730
|
+
// Update the internal value silently (don't emit during typing to avoid re-rendering)
|
|
1731
|
+
if (isSingle) {
|
|
1732
|
+
this.selectedMinute = value;
|
|
1733
|
+
}
|
|
1734
|
+
else if (isStart) {
|
|
1735
|
+
this.startMinute = value;
|
|
1736
|
+
}
|
|
1737
|
+
else {
|
|
1738
|
+
this.endMinute = value;
|
|
1739
|
+
}
|
|
1740
|
+
// Don't update dates or emit during typing - wait for blur or apply
|
|
1741
|
+
}
|
|
1742
|
+
onMinuteBlur(event, isStart = true, isSingle = false) {
|
|
1743
|
+
const key = `${isStart ? 'start' : 'end'}_${isSingle ? 'single' : 'dual'}`;
|
|
1744
|
+
const inputValue = event.target.value;
|
|
1745
|
+
if (inputValue === '' || inputValue === null || inputValue === undefined) {
|
|
1746
|
+
// If empty, set to current value
|
|
1747
|
+
const currentValue = isSingle ? this.selectedMinute : (isStart ? this.startMinute : this.endMinute);
|
|
1748
|
+
event.target.value = currentValue.toString().padStart(2, '0');
|
|
1749
|
+
delete this.minuteInputValues[key];
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
const digitsOnly = inputValue.replace(/\D/g, '');
|
|
1753
|
+
let value = parseInt(digitsOnly) || 0;
|
|
1754
|
+
if (value < 0)
|
|
1755
|
+
value = 0;
|
|
1756
|
+
if (value > 59)
|
|
1757
|
+
value = 59;
|
|
1758
|
+
// Format to 2 digits on blur (01-09 becomes 01-09, 10-59 stays as is)
|
|
1759
|
+
event.target.value = value.toString().padStart(2, '0');
|
|
1760
|
+
delete this.minuteInputValues[key]; // Clear raw input, use formatted value
|
|
1761
|
+
// Update the value
|
|
1762
|
+
if (isSingle) {
|
|
1763
|
+
this.selectedMinute = value;
|
|
1764
|
+
if (this.startDate) {
|
|
1765
|
+
let h = this.selectedHour;
|
|
1766
|
+
if (this.selectedAMPM === 'PM' && h < 12)
|
|
1767
|
+
h += 12;
|
|
1768
|
+
if (this.selectedAMPM === 'AM' && h === 12)
|
|
1769
|
+
h = 0;
|
|
1770
|
+
this.startDate.setHours(h, this.selectedMinute, this.selectedSecond);
|
|
1771
|
+
this.emitSelection();
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
else if (isStart) {
|
|
1775
|
+
this.startMinute = value;
|
|
1776
|
+
if (this.startDate) {
|
|
1777
|
+
let h = this.startHour;
|
|
1778
|
+
if (this.startAMPM === 'PM' && h < 12)
|
|
1779
|
+
h += 12;
|
|
1780
|
+
if (this.startAMPM === 'AM' && h === 12)
|
|
1781
|
+
h = 0;
|
|
1782
|
+
this.startDate.setHours(h, this.startMinute, this.startSecond);
|
|
1783
|
+
this.emitSelection();
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
else {
|
|
1787
|
+
this.endMinute = value;
|
|
1788
|
+
if (this.endDate) {
|
|
1789
|
+
let h = this.endHour;
|
|
1790
|
+
if (this.endAMPM === 'PM' && h < 12)
|
|
1791
|
+
h += 12;
|
|
1792
|
+
if (this.endAMPM === 'AM' && h === 12)
|
|
1793
|
+
h = 0;
|
|
1794
|
+
this.endDate.setHours(h, this.endMinute, this.endSecond);
|
|
1795
|
+
this.emitSelection();
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
// Get display value for hour (always 12-hour format)
|
|
1800
|
+
getDisplayHour(hour) {
|
|
1801
|
+
if (hour === 0)
|
|
1802
|
+
return 12;
|
|
1803
|
+
if (hour > 12)
|
|
1804
|
+
return hour - 12;
|
|
1805
|
+
return hour;
|
|
1806
|
+
}
|
|
1807
|
+
// Get display value for minute (formatted only when not actively typing)
|
|
1808
|
+
getMinuteDisplayValue(isStart, isSingle) {
|
|
1809
|
+
const key = `${isStart ? 'start' : 'end'}_${isSingle ? 'single' : 'dual'}`;
|
|
1810
|
+
// If user is typing (has raw input), show that, otherwise show formatted value
|
|
1811
|
+
if (this.minuteInputValues[key] !== undefined) {
|
|
1812
|
+
return this.minuteInputValues[key];
|
|
1813
|
+
}
|
|
1814
|
+
// Otherwise return formatted value
|
|
1815
|
+
const value = isSingle ? this.selectedMinute : (isStart ? this.startMinute : this.endMinute);
|
|
1816
|
+
return value.toString().padStart(2, '0');
|
|
1817
|
+
}
|
|
1818
|
+
// Helper method to apply time picker values to a date
|
|
1819
|
+
applyTimeToDate(date, isStart) {
|
|
1820
|
+
if (this.dualCalendar) {
|
|
1821
|
+
if (isStart) {
|
|
1822
|
+
let h = this.startHour;
|
|
1823
|
+
if (this.startAMPM === 'PM' && h < 12)
|
|
1824
|
+
h += 12;
|
|
1825
|
+
if (this.startAMPM === 'AM' && h === 12)
|
|
1826
|
+
h = 0;
|
|
1827
|
+
date.setHours(h, this.startMinute, this.startSecond);
|
|
1828
|
+
}
|
|
1829
|
+
else {
|
|
1830
|
+
let h = this.endHour;
|
|
1831
|
+
if (this.endAMPM === 'PM' && h < 12)
|
|
1832
|
+
h += 12;
|
|
1833
|
+
if (this.endAMPM === 'AM' && h === 12)
|
|
1834
|
+
h = 0;
|
|
1835
|
+
date.setHours(h, this.endMinute, this.endSecond);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
else {
|
|
1839
|
+
let h = this.selectedHour;
|
|
1840
|
+
if (this.selectedAMPM === 'PM' && h < 12)
|
|
1841
|
+
h += 12;
|
|
1842
|
+
if (this.selectedAMPM === 'AM' && h === 12)
|
|
1843
|
+
h = 0;
|
|
1844
|
+
date.setHours(h, this.selectedMinute, this.selectedSecond);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
// Select all text on focus for easy replacement
|
|
1848
|
+
onTimeInputFocus(event) {
|
|
1849
|
+
event.target.select();
|
|
1850
|
+
}
|
|
1851
|
+
// Format all minute inputs to 2 digits (called before Apply)
|
|
1852
|
+
formatAllMinuteInputs() {
|
|
1853
|
+
// Format minute inputs in the DOM - find all minute inputs and format single digits
|
|
1854
|
+
const inputs = document.querySelectorAll('.time-input');
|
|
1855
|
+
inputs.forEach((input) => {
|
|
1856
|
+
const value = parseInt(input.value);
|
|
1857
|
+
// If it's a valid minute value (0-59) and not already formatted (single digit or 2 digits without leading zero)
|
|
1858
|
+
if (!isNaN(value) && value >= 0 && value <= 59) {
|
|
1859
|
+
// Format if it's a single digit (1-9) or if it's 2 digits but the first is not 0
|
|
1860
|
+
if (input.value.length === 1 || (input.value.length === 2 && !input.value.startsWith('0') && value < 10)) {
|
|
1861
|
+
input.value = value.toString().padStart(2, '0');
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
formatDateToString(date) {
|
|
1867
|
+
const yyyy = date.getFullYear();
|
|
1868
|
+
const mm = (date.getMonth() + 1).toString().padStart(2, '0'); // month is 0-based
|
|
1869
|
+
const dd = date.getDate().toString().padStart(2, '0');
|
|
1870
|
+
return `${yyyy}-${mm}-${dd}`;
|
|
1871
|
+
}
|
|
1872
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkCustomCalendar, deps: [{ token: CalendarManagerService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1873
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: BkCustomCalendar, isStandalone: true, selector: "bk-custom-calendar", inputs: { enableTimepicker: "enableTimepicker", autoApply: "autoApply", closeOnAutoApply: "closeOnAutoApply", showCancel: "showCancel", linkedCalendars: "linkedCalendars", singleDatePicker: "singleDatePicker", showWeekNumbers: "showWeekNumbers", showISOWeekNumbers: "showISOWeekNumbers", customRangeDirection: "customRangeDirection", lockStartDate: "lockStartDate", position: "position", drop: "drop", dualCalendar: "dualCalendar", showRanges: "showRanges", timeFormat: "timeFormat", enableSeconds: "enableSeconds", customRanges: "customRanges", multiDateSelection: "multiDateSelection", maxDate: "maxDate", minDate: "minDate", placeholder: "placeholder", opens: "opens", inline: "inline", isDisplayCrossIcon: "isDisplayCrossIcon", selectedValue: "selectedValue", displayFormat: "displayFormat", startDate: "startDate", endDate: "endDate" }, outputs: { selected: "selected", opened: "opened", closed: "closed" }, host: { listeners: { "document:click": "onClickOutside($event)" } }, usesOnChanges: true, ngImport: i0, template: "<div class=\"calendar-container relative\" [class.open]=\"show\" [class.inline-mode]=\"inline\">\r\n <!-- Input field -->\r\n <div class=\"input-wrapper\" *ngIf=\"!inline\">\r\n <input\r\n type=\"text\"\r\n (click)=\"toggle()\"\r\n readonly\r\n [value]=\"getDisplayValue()\"\r\n [placeholder]=\"placeholder\"\r\n class=\"calendar-input\">\r\n <!-- *ngIf=\"!getDisplayValue()\" -->\r\n\r\n <span class=\"calendar-icon\" >\r\n <img alt=\"calendar\" class=\"calendar-icon-img\" [src]='brickclayIcons.calenderIcon'/>\r\n </span>\r\n <button class=\"clear-btn\" *ngIf=\"getDisplayValue() && isDisplayCrossIcon\" (click)=\"clear(); $event.stopPropagation()\" title=\"Clear\">\u00D7</button>\r\n </div>\r\n\r\n <!-- Calendar Popup / Inline -->\r\n <div class=\"calendar-popup\"\r\n [class.inline-calendar]=\"inline\"\r\n [ngClass]=\"{\r\n 'position-right': !inline && opens === 'right',\r\n 'position-center': !inline && opens === 'center',\r\n 'drop-up': !inline && drop === 'up',\r\n 'has-ranges': showRanges && customRanges,\r\n 'dual-calendar-mode': dualCalendar\r\n }\"\r\n *ngIf=\"inline || show\">\r\n\r\n <!-- RANGES -->\r\n <div class=\"ranges\" *ngIf=\"showRanges && customRanges\">\r\n <button\r\n *ngFor=\"let rangeKey of rangeOrder\"\r\n (click)=\"chooseRange(rangeKey)\"\r\n [class.active]=\"activeRange === rangeKey\"\r\n [class.custom-range]=\"rangeKey === 'Custom Range'\"\r\n class=\"range-btn\"\r\n [disabled]=\"rangeKey === 'Custom Range'\">\r\n {{ rangeKey }}\r\n </button>\r\n </div>\r\n<div class=\"\" [ngClass]=\"showRanges ? 'w-100 flex-grow-1' : ''\">\r\n\r\n\r\n <!-- SINGLE CALENDAR -->\r\n <div *ngIf=\"!dualCalendar\" class=\"calendar-wrapper\">\r\n <div class=\"header\">\r\n <!-- <button (click)=\"prevMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img src=\"assets/calender/pagination-left-gray.svg\" alt=\"arrow-left\" class=\"arrow-left\">\r\n </button> -->\r\n <button class=\"nav-btn\" type=\"button\" (click)=\"prevMonth()\" matTooltip=\"Prev month\"\r\n >\r\n <img alt=\"prev\" class=\"h-3 w-3\" [src]=\"brickclayIcons.arrowleft\"/>\r\n </button>\r\n <span class=\"month-year\">{{ getMonthName(month) }} {{ year }}</span>\r\n <!-- <button (click)=\"nextMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img src=\"assets/calender/pagination-right-gray.svg\" alt=\"arrow-right\" class=\"arrow-right\">\r\n </button> -->\r\n <button class=\"nav-btn\" type=\"button\" (click)=\"nextMonth()\" matTooltip=\"Next month\"\r\n >\r\n <img alt=\"next\" class=\"h-3 w-3\" [src]='brickclayIcons.arrowRight'/>\r\n <!--<img src=\"assets/calender/pagination-right-gray.svg\" alt=\"next\" class=\"h-3 w-3\" /> -->\r\n </button>\r\n </div>\r\n\r\n <table class=\"calendar-table\">\r\n <thead>\r\n <tr>\r\n <th *ngFor=\"let d of ['Mo','Tu','We','Th','Fr','Sa','Su']\" class=\"weekday-header\">{{ d }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr *ngFor=\"let week of calendar\">\r\n <td\r\n *ngFor=\"let dayObj of week\"\r\n (click)=\"dayObj.currentMonth && !isDateDisabled(year, month, dayObj.day) && selectDate(dayObj.day)\"\r\n (mouseenter)=\"dayObj.currentMonth && !isDateDisabled(year, month, dayObj.day) && onDateHover(dayObj.day, false)\"\r\n (mouseleave)=\"onDateLeave()\"\r\n [class.active]=\"dayObj.currentMonth && isDateSelected(year, month, dayObj.day)\"\r\n [class.in-range]=\"dayObj.currentMonth && isDateInRange(year, month, dayObj.day)\"\r\n [class.other-month]=\"!dayObj.currentMonth\"\r\n [class.disabled]=\"isDateDisabled(year, month, dayObj.day)\"\r\n [class.multi-selected]=\"multiDateSelection && isDateInMultiSelection(year, month, dayObj.day)\"\r\n [class.today]=\"dayObj.currentMonth && isToday(year, month, dayObj.day)\"\r\n class=\"calendar-day\">\r\n {{ dayObj.day }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <!-- Single Calendar Time Picker -->\r\n <div *ngIf=\"enableTimepicker\" class=\"timepicker-section\">\r\n <div class=\"timepicker-label\">Time</div>\r\n <div class=\"timepicker-controls\">\r\n <bk-time-picker\r\n pickerId=\"single-time\"\r\n [label]=\"''\"\r\n [value]=\"getSingleTimePickerDisplay()\"\r\n [closePicker]=\"shouldClosePicker('single-time')\"\r\n (timeChange)=\"onSingleTimePickerChange($event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- DUAL CALENDAR -->\r\n <div class=\"dual-calendar\" *ngIf=\"dualCalendar\">\r\n <!-- LEFT CALENDAR -->\r\n <div class=\"calendar-left\">\r\n <div class=\"header\">\r\n <button (click)=\"prevLeftMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img alt=\"arrow-left\" class=\"arrow-left\" [src]=\"brickclayIcons.arrowleft\"/>\r\n </button>\r\n <span class=\"month-year\">{{ getMonthName(leftMonth) }} {{ leftYear }}</span>\r\n <button (click)=\"nextLeftMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img alt=\"arrow-right\" class=\"arrow-right\" [src]='brickclayIcons.arrowRight'/>\r\n </button>\r\n </div>\r\n <table class=\"calendar-table\">\r\n <thead>\r\n <tr>\r\n <th *ngFor=\"let d of ['Mo','Tu','We','Th','Fr','Sa','Su']\" class=\"weekday-header\">{{ d }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr *ngFor=\"let week of leftCalendar\">\r\n <td\r\n *ngFor=\"let dayObj of week\"\r\n (click)=\"dayObj.currentMonth && !isDateDisabled(leftYear, leftMonth, dayObj.day) && selectDate(dayObj.day, false)\"\r\n (mouseenter)=\"dayObj.currentMonth && !isDateDisabled(leftYear, leftMonth, dayObj.day) && onDateHover(dayObj.day, false)\"\r\n (mouseleave)=\"onDateLeave()\"\r\n [class.active]=\"dayObj.currentMonth && isDateSelected(leftYear, leftMonth, dayObj.day)\"\r\n [class.in-range]=\"dayObj.currentMonth && isDateInRange(leftYear, leftMonth, dayObj.day)\"\r\n [class.other-month]=\"!dayObj.currentMonth\"\r\n [class.disabled]=\"isDateDisabled(leftYear, leftMonth, dayObj.day)\"\r\n [class.multi-selected]=\"multiDateSelection && isDateInMultiSelection(leftYear, leftMonth, dayObj.day)\"\r\n [class.today]=\"dayObj.currentMonth && isToday(leftYear, leftMonth, dayObj.day)\"\r\n class=\"calendar-day\">\r\n {{ dayObj.day }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <!-- Start Time Picker for Dual Calendar -->\r\n <div *ngIf=\"enableTimepicker\" class=\"timepicker-section\">\r\n <div class=\"timepicker-label\">Start Time</div>\r\n <div class=\"timepicker-controls\">\r\n <bk-time-picker\r\n pickerId=\"dual-start\"\r\n [label]=\"''\"\r\n [value]=\"getDualTimePickerDisplay(true)\"\r\n [closePicker]=\"shouldClosePicker('dual-start')\"\r\n (timeChange)=\"onDualTimePickerChange($event, true)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- RIGHT CALENDAR -->\r\n <div class=\"calendar-right\">\r\n <div class=\"header\">\r\n <button (click)=\"prevRightMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img alt=\"arrow-left\" class=\"arrow-left\" [src]=\"brickclayIcons.arrowleft\"/>\r\n </button>\r\n <span class=\"month-year\">{{ getMonthName(rightMonth) }} {{ rightYear }}</span>\r\n <button (click)=\"nextRightMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img alt=\"arrow-right\" class=\"arrow-right\" [src]='brickclayIcons.arrowRight'/>\r\n </button>\r\n </div>\r\n <table class=\"calendar-table\">\r\n <thead>\r\n <tr>\r\n <th *ngFor=\"let d of ['Mo','Tu','We','Th','Fr','Sa','Su']\" class=\"weekday-header\">{{ d }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr *ngFor=\"let week of rightCalendar\">\r\n <td\r\n *ngFor=\"let dayObj of week\"\r\n (click)=\"dayObj.currentMonth && !isDateDisabled(rightYear, rightMonth, dayObj.day) && selectDate(dayObj.day, true)\"\r\n (mouseenter)=\"dayObj.currentMonth && !isDateDisabled(rightYear, rightMonth, dayObj.day) && onDateHover(dayObj.day, true)\"\r\n (mouseleave)=\"onDateLeave()\"\r\n [class.active]=\"dayObj.currentMonth && isDateSelected(rightYear, rightMonth, dayObj.day)\"\r\n [class.in-range]=\"dayObj.currentMonth && isDateInRange(rightYear, rightMonth, dayObj.day)\"\r\n [class.other-month]=\"!dayObj.currentMonth\"\r\n [class.disabled]=\"isDateDisabled(rightYear, rightMonth, dayObj.day)\"\r\n [class.multi-selected]=\"multiDateSelection && isDateInMultiSelection(rightYear, rightMonth, dayObj.day)\"\r\n [class.today]=\"dayObj.currentMonth && isToday(rightYear, rightMonth, dayObj.day)\"\r\n class=\"calendar-day\">\r\n {{ dayObj.day }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <!-- End Time Picker for Dual Calendar -->\r\n <div *ngIf=\"enableTimepicker\" class=\"timepicker-section\">\r\n <div class=\"timepicker-label\">End Time</div>\r\n <div class=\"timepicker-controls\">\r\n <bk-time-picker\r\n pickerId=\"dual-end\"\r\n [label]=\"''\"\r\n [value]=\"getDualTimePickerDisplay(false)\"\r\n [closePicker]=\"shouldClosePicker('dual-end')\"\r\n (timeChange)=\"onDualTimePickerChange($event, false)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- FOOTER -->\r\n <div class=\"footer\" *ngIf=\"!inline\">\r\n <button *ngIf=\"showCancel\" (click)=\"cancel()\" class=\"btn-cancel\" type=\"button\">Cancel</button>\r\n <button (click)=\"apply()\" class=\"btn-apply\" type=\"button\">Apply</button>\r\n </div>\r\n\r\n </div>\r\n\r\n </div>\r\n</div>\r\n", styles: [".calendar-container,.calendar-container *{font-family:Inter,sans-serif!important}.calendar-container{position:relative;display:inline-block;width:100%}.input-wrapper{position:relative;display:flex;align-items:center}.calendar-input{width:100%;padding:9px 14px 9px 40px;border:1px solid #ddd;border-radius:8px;font-size:14px;cursor:pointer;background:#fff;transition:all .2s}.calendar-input:hover{border-color:#999}.calendar-input:focus{outline:none;border-color:#999;box-shadow:0 0 0 3px #6a6a6a1a}.calendar-icon{position:absolute;left:12px;pointer-events:none;font-size:18px}.clear-btn{position:absolute;right:9px;background:none;border:none;font-size:20px;color:#999;cursor:pointer;padding:0;width:20px;height:20px;display:flex;align-items:center;justify-content:center;line-height:1;transition:color .2s;top:8px}.clear-btn:hover{color:#333}.calendar-popup{position:absolute;top:110%;left:0;width:320px;background:#fff;border-radius:12px;box-shadow:0 10px 40px #00000026;z-index:1000;animation:slideDown .2s ease-out}.calendar-popup.inline-calendar{position:relative;top:0;left:0;width:100%;margin-top:0;animation:none;box-shadow:0 2px 8px #0000001a}.calendar-container.inline-mode{display:block;width:100%}.calendar-popup.dual-calendar-mode{width:600px}.calendar-popup.dual-calendar-mode.has-ranges{width:730px}.calendar-popup.has-ranges{width:450px}.calendar-popup.dual-calendar-mode.has-ranges .dual-calendar{border-left:1px solid #eee}.calendar-popup.drop-up{top:auto;bottom:110%;animation:slideUp .2s ease-out}.calendar-popup.position-right{left:auto;right:0}.calendar-popup.position-center{left:50%;transform:translate(-50%)}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes slideUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.ranges{display:flex;flex-direction:column;gap:4px;margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid #eee;min-width:150px;padding-right:8px}.range-btn{padding:7px 10px;border:1px solid transparent;background:transparent;border-radius:4px;cursor:pointer;text-align:left;font-size:14px;transition:all .2s;color:#838383;font-weight:500}.range-btn:hover{background:#f5f5f5;color:#000}.range-btn.active{background:#f0f0f0;color:#000;font-weight:500}.calendar-wrapper{padding:0 12px 12px;border-left:1px solid #eee}.header{display:flex;justify-content:space-between;align-items:center;padding:12px 0}.month-year{font-size:15px;font-weight:500;color:#333;flex:1;text-align:center;text-transform:capitalize}.nav-btn{background:none;border:none;font-size:24px;cursor:pointer;padding:11.5px 14px;color:#666;border-radius:4px;transition:all .2s;line-height:1;height:30px;width:30px;display:flex;justify-content:center;align-items:center}.nav-btn:hover{background:#f0f0f0;color:#000}.nav-btn img{width:auto;max-width:none!important}.calendar-table{width:100%;border-collapse:collapse;text-align:center}.weekday-header{font-size:12px;color:#7e7e7e;font-weight:600;padding:8px 4px;letter-spacing:.3px}.calendar-day{padding:8px 4px;font-size:14px;cursor:pointer;border-radius:6px;transition:all .2s;position:relative;color:#333;font-weight:500;line-height:1.5}.calendar-day:hover:not(.disabled):not(.other-month){background:#efefef;color:#000}.calendar-day.other-month{color:#ccc;cursor:default}.calendar-day.disabled{color:#ddd;cursor:not-allowed;opacity:.5}.calendar-day.active{background:#000!important;color:#fff!important;font-weight:600}.calendar-day.today{font-weight:600}.calendar-day.today:not(.active){background:#e5e4e4}.calendar-day.active:hover{background:#000!important}.calendar-day.in-range{background:#f5f5f5;color:#333;border-radius:0;position:relative}.calendar-day.in-range:hover{background:#e8e8e8}.calendar-day.in-range:before{content:\"\";position:absolute;inset:0;background:#f5f5f5;z-index:-1}.calendar-day.in-range:hover:before{background:#e8e8e8}.calendar-day.multi-selected{background:#4caf50;color:#fff;font-weight:600;border-radius:6px}.calendar-day.multi-selected:hover{background:#45a049}.dual-calendar{display:flex;width:100%;border-left:1px solid #eee}.calendar-left,.calendar-right{flex:1;min-width:0;padding:0 12px 12px}.calendar-popup.has-ranges{display:flex;flex-direction:row}.calendar-popup.has-ranges .ranges{margin-bottom:0;border-bottom:none;padding:10px}.calendar-popup.has-ranges .dual-calendar,.calendar-popup.has-ranges .calendar-wrapper{flex:1}.calendar-right .header{justify-content:space-between}.calendar-right .header .month-year{text-align:center;flex:1}.timepicker-section{margin-top:12px;padding-top:12px;border-top:1px solid #eee}.timepicker-label{font-size:12px;font-weight:500;color:#000;margin-bottom:4px;letter-spacing:-.28px}.custom-time-picker{display:flex;flex-direction:column;gap:8px;align-items:start}.time-input-group{display:flex;align-items:center;justify-content:center;gap:8px;background:#f8f9fa;padding:12px;border-radius:8px;border:1px solid #e0e0e0}.time-control{display:flex;flex-direction:column;align-items:center}.time-btn{background:#fff;border:1px solid #ddd;width:28px;height:20px;cursor:pointer;font-size:10px;color:#666;border-radius:4px;transition:all .2s;display:flex;align-items:center;justify-content:center;padding:0;line-height:1}.time-btn:hover{background:#e4e4e4;color:#fff;border-color:#e4e4e4}.time-btn.up{border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom:none}.time-btn.down{border-top-left-radius:0;border-top-right-radius:0;border-top:none}.time-input{width:40px;height:32px;text-align:center;border:1px solid #ddd;border-radius:4px;font-size:16px;font-weight:600;background:#fff;color:#333}.time-separator{font-size:18px;font-weight:600;color:#666;margin:0 2px}.ampm-control{display:flex;flex-direction:column;gap:4px;margin-left:8px}.ampm-btn{padding:6px 12px;border:1px solid #ddd;background:#fff;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600;color:#666;transition:all .2s;min-width:45px}.ampm-btn:hover{background:#f0f0f0}.ampm-btn.active{background:#000;color:#fff;border-color:#000}.html5-time-input{margin-top:8px;padding:8px;border:1px solid #ddd;border-radius:6px;font-size:14px;width:100%;max-width:120px}.footer{padding:12px;display:flex;justify-content:flex-end;gap:8px;border-top:1px solid #eee}.btn-cancel,.btn-apply{padding:8px 16px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;min-width:80px}.btn-cancel{background:#fff;color:#666;border:1px solid #ddd}.btn-cancel:hover{background:#f5f5f5;border-color:#bbb}.btn-apply{background:#000;color:#fff}.btn-apply:hover{background:#333}.btn-apply:active{transform:translateY(0)}@media (max-width: 768px){.calendar-popup{width:100%;max-width:320px}.calendar-popup.dual-calendar-mode{width:100%;max-width:100%}.calendar-popup.has-ranges{flex-direction:column}.calendar-popup.has-ranges .ranges{border-right:none;border-bottom:1px solid #eee;padding-right:0;margin-right:0;padding-bottom:16px;margin-bottom:16px}.dual-calendar{flex-direction:column}.time-input-group{flex-wrap:wrap;justify-content:center}}.ranges::-webkit-scrollbar{width:6px}.ranges::-webkit-scrollbar-track{background:#f1f1f1;border-radius:3px}.ranges::-webkit-scrollbar-thumb{background:#888;border-radius:3px}.ranges::-webkit-scrollbar-thumb:hover{background:#555}.w-100{width:100%}.flex-grow-1{flex-grow:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: BkTimePicker, selector: "bk-time-picker", inputs: ["value", "label", "placeholder", "position", "pickerId", "closePicker", "timeFormat", "showSeconds"], outputs: ["timeChange", "pickerOpened", "pickerClosed"] }] });
|
|
1874
|
+
}
|
|
1875
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkCustomCalendar, decorators: [{
|
|
1876
|
+
type: Component,
|
|
1877
|
+
args: [{ selector: 'bk-custom-calendar', standalone: true, imports: [CommonModule, FormsModule, BkTimePicker], template: "<div class=\"calendar-container relative\" [class.open]=\"show\" [class.inline-mode]=\"inline\">\r\n <!-- Input field -->\r\n <div class=\"input-wrapper\" *ngIf=\"!inline\">\r\n <input\r\n type=\"text\"\r\n (click)=\"toggle()\"\r\n readonly\r\n [value]=\"getDisplayValue()\"\r\n [placeholder]=\"placeholder\"\r\n class=\"calendar-input\">\r\n <!-- *ngIf=\"!getDisplayValue()\" -->\r\n\r\n <span class=\"calendar-icon\" >\r\n <img alt=\"calendar\" class=\"calendar-icon-img\" [src]='brickclayIcons.calenderIcon'/>\r\n </span>\r\n <button class=\"clear-btn\" *ngIf=\"getDisplayValue() && isDisplayCrossIcon\" (click)=\"clear(); $event.stopPropagation()\" title=\"Clear\">\u00D7</button>\r\n </div>\r\n\r\n <!-- Calendar Popup / Inline -->\r\n <div class=\"calendar-popup\"\r\n [class.inline-calendar]=\"inline\"\r\n [ngClass]=\"{\r\n 'position-right': !inline && opens === 'right',\r\n 'position-center': !inline && opens === 'center',\r\n 'drop-up': !inline && drop === 'up',\r\n 'has-ranges': showRanges && customRanges,\r\n 'dual-calendar-mode': dualCalendar\r\n }\"\r\n *ngIf=\"inline || show\">\r\n\r\n <!-- RANGES -->\r\n <div class=\"ranges\" *ngIf=\"showRanges && customRanges\">\r\n <button\r\n *ngFor=\"let rangeKey of rangeOrder\"\r\n (click)=\"chooseRange(rangeKey)\"\r\n [class.active]=\"activeRange === rangeKey\"\r\n [class.custom-range]=\"rangeKey === 'Custom Range'\"\r\n class=\"range-btn\"\r\n [disabled]=\"rangeKey === 'Custom Range'\">\r\n {{ rangeKey }}\r\n </button>\r\n </div>\r\n<div class=\"\" [ngClass]=\"showRanges ? 'w-100 flex-grow-1' : ''\">\r\n\r\n\r\n <!-- SINGLE CALENDAR -->\r\n <div *ngIf=\"!dualCalendar\" class=\"calendar-wrapper\">\r\n <div class=\"header\">\r\n <!-- <button (click)=\"prevMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img src=\"assets/calender/pagination-left-gray.svg\" alt=\"arrow-left\" class=\"arrow-left\">\r\n </button> -->\r\n <button class=\"nav-btn\" type=\"button\" (click)=\"prevMonth()\" matTooltip=\"Prev month\"\r\n >\r\n <img alt=\"prev\" class=\"h-3 w-3\" [src]=\"brickclayIcons.arrowleft\"/>\r\n </button>\r\n <span class=\"month-year\">{{ getMonthName(month) }} {{ year }}</span>\r\n <!-- <button (click)=\"nextMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img src=\"assets/calender/pagination-right-gray.svg\" alt=\"arrow-right\" class=\"arrow-right\">\r\n </button> -->\r\n <button class=\"nav-btn\" type=\"button\" (click)=\"nextMonth()\" matTooltip=\"Next month\"\r\n >\r\n <img alt=\"next\" class=\"h-3 w-3\" [src]='brickclayIcons.arrowRight'/>\r\n <!--<img src=\"assets/calender/pagination-right-gray.svg\" alt=\"next\" class=\"h-3 w-3\" /> -->\r\n </button>\r\n </div>\r\n\r\n <table class=\"calendar-table\">\r\n <thead>\r\n <tr>\r\n <th *ngFor=\"let d of ['Mo','Tu','We','Th','Fr','Sa','Su']\" class=\"weekday-header\">{{ d }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr *ngFor=\"let week of calendar\">\r\n <td\r\n *ngFor=\"let dayObj of week\"\r\n (click)=\"dayObj.currentMonth && !isDateDisabled(year, month, dayObj.day) && selectDate(dayObj.day)\"\r\n (mouseenter)=\"dayObj.currentMonth && !isDateDisabled(year, month, dayObj.day) && onDateHover(dayObj.day, false)\"\r\n (mouseleave)=\"onDateLeave()\"\r\n [class.active]=\"dayObj.currentMonth && isDateSelected(year, month, dayObj.day)\"\r\n [class.in-range]=\"dayObj.currentMonth && isDateInRange(year, month, dayObj.day)\"\r\n [class.other-month]=\"!dayObj.currentMonth\"\r\n [class.disabled]=\"isDateDisabled(year, month, dayObj.day)\"\r\n [class.multi-selected]=\"multiDateSelection && isDateInMultiSelection(year, month, dayObj.day)\"\r\n [class.today]=\"dayObj.currentMonth && isToday(year, month, dayObj.day)\"\r\n class=\"calendar-day\">\r\n {{ dayObj.day }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <!-- Single Calendar Time Picker -->\r\n <div *ngIf=\"enableTimepicker\" class=\"timepicker-section\">\r\n <div class=\"timepicker-label\">Time</div>\r\n <div class=\"timepicker-controls\">\r\n <bk-time-picker\r\n pickerId=\"single-time\"\r\n [label]=\"''\"\r\n [value]=\"getSingleTimePickerDisplay()\"\r\n [closePicker]=\"shouldClosePicker('single-time')\"\r\n (timeChange)=\"onSingleTimePickerChange($event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- DUAL CALENDAR -->\r\n <div class=\"dual-calendar\" *ngIf=\"dualCalendar\">\r\n <!-- LEFT CALENDAR -->\r\n <div class=\"calendar-left\">\r\n <div class=\"header\">\r\n <button (click)=\"prevLeftMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img alt=\"arrow-left\" class=\"arrow-left\" [src]=\"brickclayIcons.arrowleft\"/>\r\n </button>\r\n <span class=\"month-year\">{{ getMonthName(leftMonth) }} {{ leftYear }}</span>\r\n <button (click)=\"nextLeftMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img alt=\"arrow-right\" class=\"arrow-right\" [src]='brickclayIcons.arrowRight'/>\r\n </button>\r\n </div>\r\n <table class=\"calendar-table\">\r\n <thead>\r\n <tr>\r\n <th *ngFor=\"let d of ['Mo','Tu','We','Th','Fr','Sa','Su']\" class=\"weekday-header\">{{ d }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr *ngFor=\"let week of leftCalendar\">\r\n <td\r\n *ngFor=\"let dayObj of week\"\r\n (click)=\"dayObj.currentMonth && !isDateDisabled(leftYear, leftMonth, dayObj.day) && selectDate(dayObj.day, false)\"\r\n (mouseenter)=\"dayObj.currentMonth && !isDateDisabled(leftYear, leftMonth, dayObj.day) && onDateHover(dayObj.day, false)\"\r\n (mouseleave)=\"onDateLeave()\"\r\n [class.active]=\"dayObj.currentMonth && isDateSelected(leftYear, leftMonth, dayObj.day)\"\r\n [class.in-range]=\"dayObj.currentMonth && isDateInRange(leftYear, leftMonth, dayObj.day)\"\r\n [class.other-month]=\"!dayObj.currentMonth\"\r\n [class.disabled]=\"isDateDisabled(leftYear, leftMonth, dayObj.day)\"\r\n [class.multi-selected]=\"multiDateSelection && isDateInMultiSelection(leftYear, leftMonth, dayObj.day)\"\r\n [class.today]=\"dayObj.currentMonth && isToday(leftYear, leftMonth, dayObj.day)\"\r\n class=\"calendar-day\">\r\n {{ dayObj.day }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <!-- Start Time Picker for Dual Calendar -->\r\n <div *ngIf=\"enableTimepicker\" class=\"timepicker-section\">\r\n <div class=\"timepicker-label\">Start Time</div>\r\n <div class=\"timepicker-controls\">\r\n <bk-time-picker\r\n pickerId=\"dual-start\"\r\n [label]=\"''\"\r\n [value]=\"getDualTimePickerDisplay(true)\"\r\n [closePicker]=\"shouldClosePicker('dual-start')\"\r\n (timeChange)=\"onDualTimePickerChange($event, true)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- RIGHT CALENDAR -->\r\n <div class=\"calendar-right\">\r\n <div class=\"header\">\r\n <button (click)=\"prevRightMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img alt=\"arrow-left\" class=\"arrow-left\" [src]=\"brickclayIcons.arrowleft\"/>\r\n </button>\r\n <span class=\"month-year\">{{ getMonthName(rightMonth) }} {{ rightYear }}</span>\r\n <button (click)=\"nextRightMonth()\" class=\"nav-btn\" type=\"button\">\r\n <img alt=\"arrow-right\" class=\"arrow-right\" [src]='brickclayIcons.arrowRight'/>\r\n </button>\r\n </div>\r\n <table class=\"calendar-table\">\r\n <thead>\r\n <tr>\r\n <th *ngFor=\"let d of ['Mo','Tu','We','Th','Fr','Sa','Su']\" class=\"weekday-header\">{{ d }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr *ngFor=\"let week of rightCalendar\">\r\n <td\r\n *ngFor=\"let dayObj of week\"\r\n (click)=\"dayObj.currentMonth && !isDateDisabled(rightYear, rightMonth, dayObj.day) && selectDate(dayObj.day, true)\"\r\n (mouseenter)=\"dayObj.currentMonth && !isDateDisabled(rightYear, rightMonth, dayObj.day) && onDateHover(dayObj.day, true)\"\r\n (mouseleave)=\"onDateLeave()\"\r\n [class.active]=\"dayObj.currentMonth && isDateSelected(rightYear, rightMonth, dayObj.day)\"\r\n [class.in-range]=\"dayObj.currentMonth && isDateInRange(rightYear, rightMonth, dayObj.day)\"\r\n [class.other-month]=\"!dayObj.currentMonth\"\r\n [class.disabled]=\"isDateDisabled(rightYear, rightMonth, dayObj.day)\"\r\n [class.multi-selected]=\"multiDateSelection && isDateInMultiSelection(rightYear, rightMonth, dayObj.day)\"\r\n [class.today]=\"dayObj.currentMonth && isToday(rightYear, rightMonth, dayObj.day)\"\r\n class=\"calendar-day\">\r\n {{ dayObj.day }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <!-- End Time Picker for Dual Calendar -->\r\n <div *ngIf=\"enableTimepicker\" class=\"timepicker-section\">\r\n <div class=\"timepicker-label\">End Time</div>\r\n <div class=\"timepicker-controls\">\r\n <bk-time-picker\r\n pickerId=\"dual-end\"\r\n [label]=\"''\"\r\n [value]=\"getDualTimePickerDisplay(false)\"\r\n [closePicker]=\"shouldClosePicker('dual-end')\"\r\n (timeChange)=\"onDualTimePickerChange($event, false)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- FOOTER -->\r\n <div class=\"footer\" *ngIf=\"!inline\">\r\n <button *ngIf=\"showCancel\" (click)=\"cancel()\" class=\"btn-cancel\" type=\"button\">Cancel</button>\r\n <button (click)=\"apply()\" class=\"btn-apply\" type=\"button\">Apply</button>\r\n </div>\r\n\r\n </div>\r\n\r\n </div>\r\n</div>\r\n", styles: [".calendar-container,.calendar-container *{font-family:Inter,sans-serif!important}.calendar-container{position:relative;display:inline-block;width:100%}.input-wrapper{position:relative;display:flex;align-items:center}.calendar-input{width:100%;padding:9px 14px 9px 40px;border:1px solid #ddd;border-radius:8px;font-size:14px;cursor:pointer;background:#fff;transition:all .2s}.calendar-input:hover{border-color:#999}.calendar-input:focus{outline:none;border-color:#999;box-shadow:0 0 0 3px #6a6a6a1a}.calendar-icon{position:absolute;left:12px;pointer-events:none;font-size:18px}.clear-btn{position:absolute;right:9px;background:none;border:none;font-size:20px;color:#999;cursor:pointer;padding:0;width:20px;height:20px;display:flex;align-items:center;justify-content:center;line-height:1;transition:color .2s;top:8px}.clear-btn:hover{color:#333}.calendar-popup{position:absolute;top:110%;left:0;width:320px;background:#fff;border-radius:12px;box-shadow:0 10px 40px #00000026;z-index:1000;animation:slideDown .2s ease-out}.calendar-popup.inline-calendar{position:relative;top:0;left:0;width:100%;margin-top:0;animation:none;box-shadow:0 2px 8px #0000001a}.calendar-container.inline-mode{display:block;width:100%}.calendar-popup.dual-calendar-mode{width:600px}.calendar-popup.dual-calendar-mode.has-ranges{width:730px}.calendar-popup.has-ranges{width:450px}.calendar-popup.dual-calendar-mode.has-ranges .dual-calendar{border-left:1px solid #eee}.calendar-popup.drop-up{top:auto;bottom:110%;animation:slideUp .2s ease-out}.calendar-popup.position-right{left:auto;right:0}.calendar-popup.position-center{left:50%;transform:translate(-50%)}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes slideUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.ranges{display:flex;flex-direction:column;gap:4px;margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid #eee;min-width:150px;padding-right:8px}.range-btn{padding:7px 10px;border:1px solid transparent;background:transparent;border-radius:4px;cursor:pointer;text-align:left;font-size:14px;transition:all .2s;color:#838383;font-weight:500}.range-btn:hover{background:#f5f5f5;color:#000}.range-btn.active{background:#f0f0f0;color:#000;font-weight:500}.calendar-wrapper{padding:0 12px 12px;border-left:1px solid #eee}.header{display:flex;justify-content:space-between;align-items:center;padding:12px 0}.month-year{font-size:15px;font-weight:500;color:#333;flex:1;text-align:center;text-transform:capitalize}.nav-btn{background:none;border:none;font-size:24px;cursor:pointer;padding:11.5px 14px;color:#666;border-radius:4px;transition:all .2s;line-height:1;height:30px;width:30px;display:flex;justify-content:center;align-items:center}.nav-btn:hover{background:#f0f0f0;color:#000}.nav-btn img{width:auto;max-width:none!important}.calendar-table{width:100%;border-collapse:collapse;text-align:center}.weekday-header{font-size:12px;color:#7e7e7e;font-weight:600;padding:8px 4px;letter-spacing:.3px}.calendar-day{padding:8px 4px;font-size:14px;cursor:pointer;border-radius:6px;transition:all .2s;position:relative;color:#333;font-weight:500;line-height:1.5}.calendar-day:hover:not(.disabled):not(.other-month){background:#efefef;color:#000}.calendar-day.other-month{color:#ccc;cursor:default}.calendar-day.disabled{color:#ddd;cursor:not-allowed;opacity:.5}.calendar-day.active{background:#000!important;color:#fff!important;font-weight:600}.calendar-day.today{font-weight:600}.calendar-day.today:not(.active){background:#e5e4e4}.calendar-day.active:hover{background:#000!important}.calendar-day.in-range{background:#f5f5f5;color:#333;border-radius:0;position:relative}.calendar-day.in-range:hover{background:#e8e8e8}.calendar-day.in-range:before{content:\"\";position:absolute;inset:0;background:#f5f5f5;z-index:-1}.calendar-day.in-range:hover:before{background:#e8e8e8}.calendar-day.multi-selected{background:#4caf50;color:#fff;font-weight:600;border-radius:6px}.calendar-day.multi-selected:hover{background:#45a049}.dual-calendar{display:flex;width:100%;border-left:1px solid #eee}.calendar-left,.calendar-right{flex:1;min-width:0;padding:0 12px 12px}.calendar-popup.has-ranges{display:flex;flex-direction:row}.calendar-popup.has-ranges .ranges{margin-bottom:0;border-bottom:none;padding:10px}.calendar-popup.has-ranges .dual-calendar,.calendar-popup.has-ranges .calendar-wrapper{flex:1}.calendar-right .header{justify-content:space-between}.calendar-right .header .month-year{text-align:center;flex:1}.timepicker-section{margin-top:12px;padding-top:12px;border-top:1px solid #eee}.timepicker-label{font-size:12px;font-weight:500;color:#000;margin-bottom:4px;letter-spacing:-.28px}.custom-time-picker{display:flex;flex-direction:column;gap:8px;align-items:start}.time-input-group{display:flex;align-items:center;justify-content:center;gap:8px;background:#f8f9fa;padding:12px;border-radius:8px;border:1px solid #e0e0e0}.time-control{display:flex;flex-direction:column;align-items:center}.time-btn{background:#fff;border:1px solid #ddd;width:28px;height:20px;cursor:pointer;font-size:10px;color:#666;border-radius:4px;transition:all .2s;display:flex;align-items:center;justify-content:center;padding:0;line-height:1}.time-btn:hover{background:#e4e4e4;color:#fff;border-color:#e4e4e4}.time-btn.up{border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom:none}.time-btn.down{border-top-left-radius:0;border-top-right-radius:0;border-top:none}.time-input{width:40px;height:32px;text-align:center;border:1px solid #ddd;border-radius:4px;font-size:16px;font-weight:600;background:#fff;color:#333}.time-separator{font-size:18px;font-weight:600;color:#666;margin:0 2px}.ampm-control{display:flex;flex-direction:column;gap:4px;margin-left:8px}.ampm-btn{padding:6px 12px;border:1px solid #ddd;background:#fff;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600;color:#666;transition:all .2s;min-width:45px}.ampm-btn:hover{background:#f0f0f0}.ampm-btn.active{background:#000;color:#fff;border-color:#000}.html5-time-input{margin-top:8px;padding:8px;border:1px solid #ddd;border-radius:6px;font-size:14px;width:100%;max-width:120px}.footer{padding:12px;display:flex;justify-content:flex-end;gap:8px;border-top:1px solid #eee}.btn-cancel,.btn-apply{padding:8px 16px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;min-width:80px}.btn-cancel{background:#fff;color:#666;border:1px solid #ddd}.btn-cancel:hover{background:#f5f5f5;border-color:#bbb}.btn-apply{background:#000;color:#fff}.btn-apply:hover{background:#333}.btn-apply:active{transform:translateY(0)}@media (max-width: 768px){.calendar-popup{width:100%;max-width:320px}.calendar-popup.dual-calendar-mode{width:100%;max-width:100%}.calendar-popup.has-ranges{flex-direction:column}.calendar-popup.has-ranges .ranges{border-right:none;border-bottom:1px solid #eee;padding-right:0;margin-right:0;padding-bottom:16px;margin-bottom:16px}.dual-calendar{flex-direction:column}.time-input-group{flex-wrap:wrap;justify-content:center}}.ranges::-webkit-scrollbar{width:6px}.ranges::-webkit-scrollbar-track{background:#f1f1f1;border-radius:3px}.ranges::-webkit-scrollbar-thumb{background:#888;border-radius:3px}.ranges::-webkit-scrollbar-thumb:hover{background:#555}.w-100{width:100%}.flex-grow-1{flex-grow:1}\n"] }]
|
|
1878
|
+
}], ctorParameters: () => [{ type: CalendarManagerService }], propDecorators: { enableTimepicker: [{
|
|
1879
|
+
type: Input
|
|
1880
|
+
}], autoApply: [{
|
|
1881
|
+
type: Input
|
|
1882
|
+
}], closeOnAutoApply: [{
|
|
1883
|
+
type: Input
|
|
1884
|
+
}], showCancel: [{
|
|
1885
|
+
type: Input
|
|
1886
|
+
}], linkedCalendars: [{
|
|
1887
|
+
type: Input
|
|
1888
|
+
}], singleDatePicker: [{
|
|
1889
|
+
type: Input
|
|
1890
|
+
}], showWeekNumbers: [{
|
|
1891
|
+
type: Input
|
|
1892
|
+
}], showISOWeekNumbers: [{
|
|
1893
|
+
type: Input
|
|
1894
|
+
}], customRangeDirection: [{
|
|
1895
|
+
type: Input
|
|
1896
|
+
}], lockStartDate: [{
|
|
1897
|
+
type: Input
|
|
1898
|
+
}], position: [{
|
|
1899
|
+
type: Input
|
|
1900
|
+
}], drop: [{
|
|
1901
|
+
type: Input
|
|
1902
|
+
}], dualCalendar: [{
|
|
1903
|
+
type: Input
|
|
1904
|
+
}], showRanges: [{
|
|
1905
|
+
type: Input
|
|
1906
|
+
}], timeFormat: [{
|
|
1907
|
+
type: Input
|
|
1908
|
+
}], enableSeconds: [{
|
|
1909
|
+
type: Input
|
|
1910
|
+
}], customRanges: [{
|
|
1911
|
+
type: Input
|
|
1912
|
+
}], multiDateSelection: [{
|
|
1913
|
+
type: Input
|
|
1914
|
+
}], maxDate: [{
|
|
1915
|
+
type: Input
|
|
1916
|
+
}], minDate: [{
|
|
1917
|
+
type: Input
|
|
1918
|
+
}], placeholder: [{
|
|
1919
|
+
type: Input
|
|
1920
|
+
}], opens: [{
|
|
1921
|
+
type: Input
|
|
1922
|
+
}], inline: [{
|
|
1923
|
+
type: Input
|
|
1924
|
+
}], isDisplayCrossIcon: [{
|
|
1925
|
+
type: Input
|
|
1926
|
+
}], selected: [{
|
|
1927
|
+
type: Output
|
|
1928
|
+
}], opened: [{
|
|
1929
|
+
type: Output
|
|
1930
|
+
}], closed: [{
|
|
1931
|
+
type: Output
|
|
1932
|
+
}], selectedValue: [{
|
|
1933
|
+
type: Input
|
|
1934
|
+
}], displayFormat: [{
|
|
1935
|
+
type: Input
|
|
1936
|
+
}], startDate: [{
|
|
1937
|
+
type: Input
|
|
1938
|
+
}], endDate: [{
|
|
1939
|
+
type: Input
|
|
1940
|
+
}], onClickOutside: [{
|
|
1941
|
+
type: HostListener,
|
|
1942
|
+
args: ['document:click', ['$event']]
|
|
1943
|
+
}] } });
|
|
1944
|
+
|
|
1945
|
+
class BkScheduledDatePicker {
|
|
1946
|
+
timeFormat = 12;
|
|
1947
|
+
enableSeconds = false;
|
|
1948
|
+
scheduled = new EventEmitter();
|
|
1949
|
+
cleared = new EventEmitter();
|
|
1950
|
+
activeTab = 'single';
|
|
1951
|
+
openTimePickerId = null; // Track which time picker is currently open
|
|
1952
|
+
closePickerCounter = {}; // Track close signals for each picker
|
|
1953
|
+
// Single Date
|
|
1954
|
+
singleDate = null;
|
|
1955
|
+
singleAllDay = false;
|
|
1956
|
+
singleStartTime = '1:00 AM';
|
|
1957
|
+
singleEndTime = '2:00 AM';
|
|
1958
|
+
// Multiple Dates
|
|
1959
|
+
multipleDates = [];
|
|
1960
|
+
// Date Range
|
|
1961
|
+
rangeStartDate = null;
|
|
1962
|
+
rangeEndDate = null;
|
|
1963
|
+
rangeAllDay = false;
|
|
1964
|
+
rangeStartTime = '1:00 AM';
|
|
1965
|
+
rangeEndTime = '2:00 AM';
|
|
1966
|
+
ngOnInit() {
|
|
1967
|
+
// Initialize with default time if needed
|
|
1968
|
+
}
|
|
1969
|
+
onTabChange(tab) {
|
|
1970
|
+
this.activeTab = tab;
|
|
1971
|
+
this.openTimePickerId = null; // Close any open pickers when switching tabs
|
|
1972
|
+
}
|
|
1973
|
+
onTimePickerOpened(pickerId) {
|
|
1974
|
+
// Close all other pickers when one opens
|
|
1975
|
+
if (this.openTimePickerId && this.openTimePickerId !== pickerId) {
|
|
1976
|
+
// Increment close counter for the previously open picker
|
|
1977
|
+
if (!this.closePickerCounter[this.openTimePickerId]) {
|
|
1978
|
+
this.closePickerCounter[this.openTimePickerId] = 0;
|
|
1979
|
+
}
|
|
1980
|
+
this.closePickerCounter[this.openTimePickerId]++;
|
|
1981
|
+
}
|
|
1982
|
+
this.openTimePickerId = pickerId;
|
|
1983
|
+
}
|
|
1984
|
+
onTimePickerClosed(pickerId) {
|
|
1985
|
+
// Reset open picker ID if this was the open one
|
|
1986
|
+
if (this.openTimePickerId === pickerId) {
|
|
1987
|
+
this.openTimePickerId = null;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
shouldClosePicker(pickerId) {
|
|
1991
|
+
// Return the counter value for this picker (triggers change detection)
|
|
1992
|
+
return this.closePickerCounter[pickerId] || 0;
|
|
1993
|
+
}
|
|
1994
|
+
// Single Date Handlers
|
|
1995
|
+
onSingleDateSelected(event) {
|
|
1996
|
+
if (event.startDate) {
|
|
1997
|
+
this.singleDate = new Date(event.startDate);
|
|
1998
|
+
// Initialize time if not all day
|
|
1999
|
+
if (!this.singleAllDay) {
|
|
2000
|
+
this.updateSingleDateTimes();
|
|
2001
|
+
}
|
|
2002
|
+
this.emitScheduled();
|
|
2003
|
+
}
|
|
2004
|
+
else {
|
|
2005
|
+
this.singleDate = null;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
onSingleAllDayChange() {
|
|
2009
|
+
this.singleAllDay = !this.singleAllDay;
|
|
2010
|
+
if (this.singleDate) {
|
|
2011
|
+
this.updateSingleDateTimes();
|
|
2012
|
+
this.emitScheduled();
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
onSingleStartTimeChange(time) {
|
|
2016
|
+
this.singleStartTime = time;
|
|
2017
|
+
if (this.singleDate) {
|
|
2018
|
+
this.updateSingleDateTimes();
|
|
2019
|
+
this.emitScheduled();
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
onSingleEndTimeChange(time) {
|
|
2023
|
+
this.singleEndTime = time;
|
|
2024
|
+
if (this.singleDate) {
|
|
2025
|
+
this.updateSingleDateTimes();
|
|
2026
|
+
this.emitScheduled();
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
updateSingleDateTimes() {
|
|
2030
|
+
if (!this.singleDate)
|
|
2031
|
+
return;
|
|
2032
|
+
if (this.singleAllDay) {
|
|
2033
|
+
this.singleDate.setHours(0, 0, 0, 0);
|
|
2034
|
+
}
|
|
2035
|
+
else {
|
|
2036
|
+
const startTime = this.parseTimeString(this.singleStartTime);
|
|
2037
|
+
const endTime = this.parseTimeString(this.singleEndTime);
|
|
2038
|
+
this.singleDate.setHours(startTime.hours, startTime.minutes, 0, 0);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
// Multiple Dates Handlers
|
|
2042
|
+
onMultipleDatesSelected(event) {
|
|
2043
|
+
if (event.selectedDates && event.selectedDates.length > 0) {
|
|
2044
|
+
const newDates = [];
|
|
2045
|
+
event.selectedDates.forEach(date => {
|
|
2046
|
+
// const dateStr = this.getDateString(date);
|
|
2047
|
+
const dateStr = date;
|
|
2048
|
+
const existing = this.multipleDates.find(d => this.getDateString(d.date) === dateStr);
|
|
2049
|
+
if (existing) {
|
|
2050
|
+
newDates.push(existing);
|
|
2051
|
+
}
|
|
2052
|
+
else {
|
|
2053
|
+
// Create new time configuration for this date
|
|
2054
|
+
newDates.push({
|
|
2055
|
+
date: new Date(date),
|
|
2056
|
+
allDay: false,
|
|
2057
|
+
startTime: '1:00 AM',
|
|
2058
|
+
endTime: '2:00 AM'
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
});
|
|
2062
|
+
this.multipleDates = newDates;
|
|
2063
|
+
this.emitScheduled();
|
|
2064
|
+
}
|
|
2065
|
+
else {
|
|
2066
|
+
this.multipleDates = [];
|
|
2067
|
+
this.emitScheduled();
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
onMultipleDateAllDayChange(index) {
|
|
2071
|
+
if (this.multipleDates[index]) {
|
|
2072
|
+
this.multipleDates[index].allDay = !this.multipleDates[index].allDay;
|
|
2073
|
+
if (this.multipleDates[index].allDay) {
|
|
2074
|
+
this.multipleDates[index].date.setHours(0, 0, 0, 0);
|
|
2075
|
+
}
|
|
2076
|
+
else {
|
|
2077
|
+
const time = this.parseTimeString(this.multipleDates[index].startTime);
|
|
2078
|
+
this.multipleDates[index].date.setHours(time.hours, time.minutes, 0, 0);
|
|
2079
|
+
}
|
|
2080
|
+
this.emitScheduled();
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
onMultipleDateStartTimeChange(index, time) {
|
|
2084
|
+
if (this.multipleDates[index]) {
|
|
2085
|
+
this.multipleDates[index].startTime = time;
|
|
2086
|
+
if (!this.multipleDates[index].allDay) {
|
|
2087
|
+
const parsed = this.parseTimeString(time);
|
|
2088
|
+
this.multipleDates[index].date.setHours(parsed.hours, parsed.minutes, 0, 0);
|
|
2089
|
+
}
|
|
2090
|
+
this.emitScheduled();
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
onMultipleDateEndTimeChange(index, time) {
|
|
2094
|
+
if (this.multipleDates[index]) {
|
|
2095
|
+
this.multipleDates[index].endTime = time;
|
|
2096
|
+
this.emitScheduled();
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
// Date Range Handlers
|
|
2100
|
+
onRangeSelected(event) {
|
|
2101
|
+
if (event.startDate && event.endDate) {
|
|
2102
|
+
this.rangeStartDate = new Date(event.startDate);
|
|
2103
|
+
this.rangeEndDate = new Date(event.endDate);
|
|
2104
|
+
if (!this.rangeAllDay) {
|
|
2105
|
+
this.updateRangeTimes();
|
|
2106
|
+
}
|
|
2107
|
+
this.emitScheduled();
|
|
2108
|
+
}
|
|
2109
|
+
else {
|
|
2110
|
+
this.rangeStartDate = null;
|
|
2111
|
+
this.rangeEndDate = null;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
onRangeAllDayChange() {
|
|
2115
|
+
this.rangeAllDay = !this.rangeAllDay;
|
|
2116
|
+
if (this.rangeStartDate && this.rangeEndDate) {
|
|
2117
|
+
this.updateRangeTimes();
|
|
2118
|
+
this.emitScheduled();
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
onRangeStartTimeChange(time) {
|
|
2122
|
+
this.rangeStartTime = time;
|
|
2123
|
+
if (this.rangeStartDate && !this.rangeAllDay) {
|
|
2124
|
+
this.updateRangeTimes();
|
|
2125
|
+
this.emitScheduled();
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
onRangeEndTimeChange(time) {
|
|
2129
|
+
this.rangeEndTime = time;
|
|
2130
|
+
if (this.rangeEndDate && !this.rangeAllDay) {
|
|
2131
|
+
this.updateRangeTimes();
|
|
2132
|
+
this.emitScheduled();
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
updateRangeTimes() {
|
|
2136
|
+
if (!this.rangeStartDate || !this.rangeEndDate)
|
|
2137
|
+
return;
|
|
2138
|
+
if (this.rangeAllDay) {
|
|
2139
|
+
this.rangeStartDate.setHours(0, 0, 0, 0);
|
|
2140
|
+
this.rangeEndDate.setHours(23, 59, 59, 999);
|
|
2141
|
+
}
|
|
2142
|
+
else {
|
|
2143
|
+
const startTime = this.parseTimeString(this.rangeStartTime);
|
|
2144
|
+
const endTime = this.parseTimeString(this.rangeEndTime);
|
|
2145
|
+
this.rangeStartDate.setHours(startTime.hours, startTime.minutes, 0, 0);
|
|
2146
|
+
this.rangeEndDate.setHours(endTime.hours, endTime.minutes, 0, 0);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
// Utility Methods
|
|
2150
|
+
parseTimeString(timeStr) {
|
|
2151
|
+
// Parse time string like "7:01 AM" or "19:01"
|
|
2152
|
+
const parts = timeStr.split(' ');
|
|
2153
|
+
let timePart = parts[0];
|
|
2154
|
+
const ampm = parts[1];
|
|
2155
|
+
const [hoursStr, minutesStr] = timePart.split(':');
|
|
2156
|
+
let hours = parseInt(hoursStr, 10);
|
|
2157
|
+
const minutes = parseInt(minutesStr || '0', 10);
|
|
2158
|
+
if (ampm) {
|
|
2159
|
+
if (ampm.toUpperCase() === 'PM' && hours !== 12) {
|
|
2160
|
+
hours += 12;
|
|
2161
|
+
}
|
|
2162
|
+
else if (ampm.toUpperCase() === 'AM' && hours === 12) {
|
|
2163
|
+
hours = 0;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
return { hours, minutes };
|
|
2167
|
+
}
|
|
2168
|
+
getDateString(date) {
|
|
2169
|
+
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
|
2170
|
+
}
|
|
2171
|
+
formatDate(date) {
|
|
2172
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
2173
|
+
return `${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;
|
|
2174
|
+
}
|
|
2175
|
+
emitScheduled() {
|
|
2176
|
+
const selection = {
|
|
2177
|
+
mode: this.activeTab
|
|
2178
|
+
};
|
|
2179
|
+
if (this.activeTab === 'single' && this.singleDate) {
|
|
2180
|
+
// For single date, create startDate and endDate with same date but different times
|
|
2181
|
+
const startDate = new Date(this.singleDate);
|
|
2182
|
+
const endDate = new Date(this.singleDate);
|
|
2183
|
+
if (!this.singleAllDay) {
|
|
2184
|
+
const startTime = this.parseTimeString(this.singleStartTime);
|
|
2185
|
+
const endTime = this.parseTimeString(this.singleEndTime);
|
|
2186
|
+
startDate.setHours(startTime.hours, startTime.minutes, 0, 0);
|
|
2187
|
+
endDate.setHours(endTime.hours, endTime.minutes, 0, 0);
|
|
2188
|
+
}
|
|
2189
|
+
else {
|
|
2190
|
+
startDate.setHours(0, 0, 0, 0);
|
|
2191
|
+
endDate.setHours(23, 59, 59, 999);
|
|
2192
|
+
}
|
|
2193
|
+
selection.singleDate = {
|
|
2194
|
+
startDate: startDate,
|
|
2195
|
+
endDate: endDate,
|
|
2196
|
+
allDay: this.singleAllDay,
|
|
2197
|
+
startTime: this.singleStartTime,
|
|
2198
|
+
endTime: this.singleEndTime
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
else if (this.activeTab === 'multiple') {
|
|
2202
|
+
selection.multipleDates = this.multipleDates.map(d => ({
|
|
2203
|
+
date: new Date(d.date),
|
|
2204
|
+
allDay: d.allDay,
|
|
2205
|
+
startTime: d.startTime,
|
|
2206
|
+
endTime: d.endTime
|
|
2207
|
+
}));
|
|
2208
|
+
}
|
|
2209
|
+
else if (this.activeTab === 'range' && this.rangeStartDate && this.rangeEndDate) {
|
|
2210
|
+
selection.dateRange = {
|
|
2211
|
+
startDate: new Date(this.rangeStartDate),
|
|
2212
|
+
endDate: new Date(this.rangeEndDate),
|
|
2213
|
+
allDay: this.rangeAllDay,
|
|
2214
|
+
startTime: this.rangeStartTime,
|
|
2215
|
+
endTime: this.rangeEndTime
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
this.scheduled.emit(selection);
|
|
2219
|
+
}
|
|
2220
|
+
clear() {
|
|
2221
|
+
this.singleDate = null;
|
|
2222
|
+
this.multipleDates = [];
|
|
2223
|
+
this.rangeStartDate = null;
|
|
2224
|
+
this.rangeEndDate = null;
|
|
2225
|
+
this.singleAllDay = false;
|
|
2226
|
+
this.rangeAllDay = false;
|
|
2227
|
+
this.singleStartTime = '1:00 AM';
|
|
2228
|
+
this.singleEndTime = '2:00 AM';
|
|
2229
|
+
this.rangeStartTime = '1:00 AM';
|
|
2230
|
+
this.rangeEndTime = '2:00 AM';
|
|
2231
|
+
this.cleared.emit();
|
|
2232
|
+
}
|
|
2233
|
+
apply() {
|
|
2234
|
+
this.emitScheduled();
|
|
2235
|
+
}
|
|
2236
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkScheduledDatePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2237
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: BkScheduledDatePicker, isStandalone: true, selector: "bk-scheduled-date-picker", inputs: { timeFormat: "timeFormat", enableSeconds: "enableSeconds" }, outputs: { scheduled: "scheduled", cleared: "cleared" }, ngImport: i0, template: "<div class=\"scheduled-date-picker-container\">\r\n <!-- Header with Tabs -->\r\n\r\n\r\n <!-- Main Content Area -->\r\n\r\n <div class=\"scheduled-content\">\r\n <!-- Left Side: Calendar -->\r\n <div class=\"calendar-section\">\r\n <h2 class=\"scheduled-title\">Scheduled Dates</h2>\r\n <div class=\"tabs\">\r\n <button\r\n class=\"tab-button\"\r\n [class.active]=\"activeTab === 'single'\"\r\n (click)=\"onTabChange('single')\">\r\n Single Date\r\n </button>\r\n <button\r\n class=\"tab-button\"\r\n [class.active]=\"activeTab === 'multiple'\"\r\n (click)=\"onTabChange('multiple')\">\r\n Multiple Dates\r\n </button>\r\n <button\r\n class=\"tab-button\"\r\n [class.active]=\"activeTab === 'range'\"\r\n (click)=\"onTabChange('range')\">\r\n Date Range\r\n </button>\r\n </div>\r\n <!-- Single Date Calendar -->\r\n <div *ngIf=\"activeTab === 'single'\" class=\"calendar-wrapper-inline\">\r\n <bk-custom-calendar\r\n [inline]=\"true\"\r\n [dualCalendar]=\"false\"\r\n [singleDatePicker]=\"true\"\r\n [showRanges]=\"false\"\r\n [enableTimepicker]=\"false\"\r\n [showCancel]=\"false\"\r\n placeholder=\"Select a date\"\r\n (selected)=\"onSingleDateSelected($event)\">\r\n </bk-custom-calendar>\r\n </div>\r\n\r\n <!-- Multiple Dates Calendar -->\r\n <div *ngIf=\"activeTab === 'multiple'\" class=\"calendar-wrapper-inline\">\r\n <bk-custom-calendar\r\n [inline]=\"true\"\r\n [dualCalendar]=\"false\"\r\n [singleDatePicker]=\"false\"\r\n [showRanges]=\"false\"\r\n [enableTimepicker]=\"false\"\r\n [multiDateSelection]=\"true\"\r\n [showCancel]=\"false\"\r\n placeholder=\"Select multiple dates\"\r\n (selected)=\"onMultipleDatesSelected($event)\">\r\n </bk-custom-calendar>\r\n </div>\r\n\r\n <!-- Date Range Calendar -->\r\n <div *ngIf=\"activeTab === 'range'\" class=\"calendar-wrapper-inline\">\r\n <bk-custom-calendar\r\n [inline]=\"true\"\r\n [dualCalendar]=\"false\"\r\n [singleDatePicker]=\"false\"\r\n [showRanges]=\"false\"\r\n [enableTimepicker]=\"false\"\r\n [showCancel]=\"false\"\r\n placeholder=\"Select date range\"\r\n (selected)=\"onRangeSelected($event)\">\r\n </bk-custom-calendar>\r\n </div>\r\n </div>\r\n\r\n <!-- Right Side: Time Configuration -->\r\n <div class=\"time-config-section\">\r\n <h3 class=\"time-config-title\">Time Configuration</h3>\r\n\r\n <!-- Single Date Time Configuration -->\r\n <div *ngIf=\"activeTab === 'single'\">\r\n <div *ngIf=\"singleDate\" class=\"time-config-item\">\r\n <div class=\"time-config-header\">\r\n <span class=\"date-label\">{{ formatDate(singleDate) }}</span>\r\n <label class=\"all-day-toggle\">\r\n <span class=\"toggle-label\">All Day</span>\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"singleAllDay\"\r\n (change)=\"onSingleAllDayChange()\">\r\n </label>\r\n </div>\r\n <div *ngIf=\"!singleAllDay\" class=\"time-inputs\">\r\n <bk-time-picker\r\n pickerId=\"single-start\"\r\n label=\"Start Time\"\r\n [value]=\"singleStartTime\"\r\n [position]=\"'left'\"\r\n [closePicker]=\"shouldClosePicker('single-start')\"\r\n (timeChange)=\"onSingleStartTimeChange($event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n <bk-time-picker\r\n pickerId=\"single-end\"\r\n label=\"End Time\"\r\n [value]=\"singleEndTime\"\r\n [position]=\"'right'\"\r\n [closePicker]=\"shouldClosePicker('single-end')\"\r\n (timeChange)=\"onSingleEndTimeChange($event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n <div *ngIf=\"!singleDate\" class=\"no-selection\">\r\n <p>No date selected. Select a date from the calendar.</p>\r\n </div>\r\n </div>\r\n\r\n <!-- Multiple Dates Time Configuration -->\r\n <div *ngIf=\"activeTab === 'multiple'\" class=\"time-config-list\">\r\n <div\r\n *ngFor=\"let dateConfig of multipleDates; let i = index\"\r\n class=\"time-config-item\">\r\n <div class=\"time-config-header\">\r\n <span class=\"date-label\">{{ formatDate(dateConfig.date) }}</span>\r\n <label class=\"all-day-toggle\">\r\n <span class=\"toggle-label\">All Day</span>\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dateConfig.allDay\"\r\n (change)=\"onMultipleDateAllDayChange(i)\">\r\n </label>\r\n </div>\r\n <div *ngIf=\"!dateConfig.allDay\" class=\"time-inputs\">\r\n <bk-time-picker\r\n [pickerId]=\"'multiple-' + i + '-start'\"\r\n label=\"Start Time\"\r\n [value]=\"dateConfig.startTime\"\r\n [position]=\"'left'\"\r\n [closePicker]=\"shouldClosePicker('multiple-' + i + '-start')\"\r\n (timeChange)=\"onMultipleDateStartTimeChange(i, $event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n <bk-time-picker\r\n [pickerId]=\"'multiple-' + i + '-end'\"\r\n label=\"End Time\"\r\n [value]=\"dateConfig.endTime\"\r\n [position]=\"'right'\"\r\n [closePicker]=\"shouldClosePicker('multiple-' + i + '-end')\"\r\n (timeChange)=\"onMultipleDateEndTimeChange(i, $event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n <div *ngIf=\"multipleDates.length === 0\" class=\"no-selection\">\r\n <p>No dates selected. Select dates from the calendar.</p>\r\n </div>\r\n </div>\r\n\r\n <!-- Date Range Time Configuration -->\r\n <div *ngIf=\"activeTab === 'range' && rangeStartDate && rangeEndDate\" class=\"time-config-item\">\r\n <div class=\"time-config-header\">\r\n <span class=\"date-label\">{{ formatDate(rangeStartDate) }} - {{ formatDate(rangeEndDate) }}</span>\r\n <label class=\"all-day-toggle\">\r\n <span class=\"toggle-label\">All Day</span>\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"rangeAllDay\"\r\n (change)=\"onRangeAllDayChange()\">\r\n </label>\r\n </div>\r\n <div *ngIf=\"!rangeAllDay\" class=\"time-inputs\">\r\n <bk-time-picker\r\n pickerId=\"range-start\"\r\n label=\"Start Time\"\r\n [value]=\"rangeStartTime\"\r\n [position]=\"'left'\"\r\n [closePicker]=\"shouldClosePicker('range-start')\"\r\n (timeChange)=\"onRangeStartTimeChange($event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n <bk-time-picker\r\n pickerId=\"range-end\"\r\n label=\"End Time\"\r\n [value]=\"rangeEndTime\"\r\n [position]=\"'right'\"\r\n [closePicker]=\"shouldClosePicker('range-end')\"\r\n (timeChange)=\"onRangeEndTimeChange($event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n <div *ngIf=\"activeTab === 'range' && (!rangeStartDate || !rangeEndDate)\" class=\"no-selection\">\r\n <p>No date range selected. Select a date range from the calendar.</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Action Buttons -->\r\n <div class=\"action-buttons\">\r\n <button class=\"btn-clear\" (click)=\"clear()\">Clear</button>\r\n <button class=\"btn-apply\" (click)=\"apply()\">Apply</button>\r\n </div>\r\n</div>\r\n\r\n", styles: [".scheduled-date-picker-container{font-family:Inter,sans-serif;background:#fff;border-radius:12px;padding:0;box-shadow:0 2px 8px #0000001a;overflow:hidden;width:100%;max-width:100%;box-sizing:border-box}.scheduled-header{padding:24px 24px 16px;border-bottom:1px solid #e5e7eb;background:#fff}.scheduled-title{font-size:18px;font-weight:500;line-height:26px;color:#111827;letter-spacing:-.28px;margin:0 0 16px}.tabs{display:flex;margin-bottom:16px;border-radius:6px;padding:3px;background-color:#54578e12}.tab-button{padding:5px 11px;border:none;background:transparent;color:#6b7080;font-size:11px;font-weight:500;cursor:pointer;border:1px solid transparent;transition:all .2s;font-family:Inter,sans-serif;flex:1;border-radius:4px}.tab-button.active{color:#15191e;border-color:#42578a26;background:#fff}.scheduled-content{display:flex;gap:0;align-items:stretch}.calendar-section{flex:0 0 55%;max-width:55%;padding:12px;border-right:1px solid #e5e7eb;background:#fff;box-sizing:border-box}.calendar-wrapper-inline{width:100%}.calendar-wrapper-inline app-custom-calendar{width:100%}.time-config-section{flex:0 0 45%;max-width:45%;padding:12px;background:#fff;overflow-y:auto;max-height:600px;box-sizing:border-box}.time-config-title{font-size:16px;font-weight:600;color:#111827;margin:17px 0 14px}.time-config-item{padding:14px;border:1px solid #e5e7eb;border-radius:8px;background:#fff}.time-config-header{display:flex;justify-content:space-between;align-items:center}.date-label{font-size:12px;font-weight:500;color:#15191e;letter-spacing:-.28px}.all-day-toggle{display:flex;align-items:center;gap:5px;cursor:pointer;-webkit-user-select:none;user-select:none}.all-day-toggle input[type=checkbox]{width:28px;height:16px;appearance:none;background:#bbbdc5;border-radius:10px;position:relative;cursor:pointer;transition:background .2s;margin:0}.all-day-toggle input[type=checkbox]:checked{background:#22973f}.all-day-toggle input[type=checkbox]:before{content:\"\";position:absolute;width:12px;height:12px;border-radius:50%;background:#fff;top:1.5px;left:2.5px;transition:transform .2s;box-shadow:0 1px 3px #0003}.all-day-toggle input[type=checkbox]:checked:before{transform:translate(12px)}.toggle-label{font-size:12px;font-weight:500;color:#111827}.all-day-toggle input[type=checkbox]:checked+.toggle-label{color:#111827}.time-inputs{display:flex;gap:14px;margin-top:12px}.time-config-list{display:flex;flex-direction:column;gap:14px;max-height:350px;overflow-y:auto;padding-right:4px}.time-config-list::-webkit-scrollbar{width:6px;height:6px}.time-config-list::-webkit-scrollbar-track{background:#f1f1f1;border-radius:3px}.time-config-list::-webkit-scrollbar-thumb{background:#b4b4b4;border-radius:3px}.time-config-list::-webkit-scrollbar-thumb:hover{background:#9b9b9b}.no-selection{padding:24px;text-align:center;color:#9ca3af;font-size:14px}.action-buttons{display:flex;justify-content:flex-end;gap:12px;padding:12px;border-top:1px solid #e5e7eb;background:#fff}.btn-clear,.btn-apply{padding:10px 20px;border:none;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;font-family:Inter,sans-serif;min-width:80px}.btn-clear{background:#fff;color:#6b7280;border:1px solid #d1d5db}.btn-clear:hover{background:#f9fafb;border-color:#9ca3af}.btn-apply{background:#111827;color:#fff}.btn-apply:hover{background:#374151}@media (max-width: 1200px){.calendar-section{flex:0 0 52%;max-width:52%}.time-config-section{flex:0 0 48%;max-width:48%}}@media (max-width: 1024px){.scheduled-content{flex-direction:column}.calendar-section{flex:1 1 auto;max-width:100%;border-right:none;border-bottom:1px solid #e5e7eb}.time-config-section{flex:1 1 auto;max-width:100%;max-height:none}.time-config-list{max-height:320px}}@media (max-width: 768px){.scheduled-date-picker-container{border-radius:0}.scheduled-header{padding:16px}.calendar-section,.time-config-section{padding:12px 16px}.tabs{overflow-x:auto}.tab-button{white-space:nowrap;font-size:12px;padding:6px 10px}.time-inputs{flex-direction:column}.time-config-item{padding:12px}.action-buttons{padding:10px}}@media (max-width: 480px){.scheduled-title{font-size:16px}.time-config-title{font-size:14px}.date-label{font-size:11px}.time-config-list{max-height:260px}.btn-clear,.btn-apply{padding:8px 14px;font-size:13px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: BkCustomCalendar, selector: "bk-custom-calendar", inputs: ["enableTimepicker", "autoApply", "closeOnAutoApply", "showCancel", "linkedCalendars", "singleDatePicker", "showWeekNumbers", "showISOWeekNumbers", "customRangeDirection", "lockStartDate", "position", "drop", "dualCalendar", "showRanges", "timeFormat", "enableSeconds", "customRanges", "multiDateSelection", "maxDate", "minDate", "placeholder", "opens", "inline", "isDisplayCrossIcon", "selectedValue", "displayFormat", "startDate", "endDate"], outputs: ["selected", "opened", "closed"] }, { kind: "component", type: BkTimePicker, selector: "bk-time-picker", inputs: ["value", "label", "placeholder", "position", "pickerId", "closePicker", "timeFormat", "showSeconds"], outputs: ["timeChange", "pickerOpened", "pickerClosed"] }] });
|
|
2238
|
+
}
|
|
2239
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkScheduledDatePicker, decorators: [{
|
|
2240
|
+
type: Component,
|
|
2241
|
+
args: [{ selector: 'bk-scheduled-date-picker', standalone: true, imports: [CommonModule, FormsModule, BkCustomCalendar, BkTimePicker], template: "<div class=\"scheduled-date-picker-container\">\r\n <!-- Header with Tabs -->\r\n\r\n\r\n <!-- Main Content Area -->\r\n\r\n <div class=\"scheduled-content\">\r\n <!-- Left Side: Calendar -->\r\n <div class=\"calendar-section\">\r\n <h2 class=\"scheduled-title\">Scheduled Dates</h2>\r\n <div class=\"tabs\">\r\n <button\r\n class=\"tab-button\"\r\n [class.active]=\"activeTab === 'single'\"\r\n (click)=\"onTabChange('single')\">\r\n Single Date\r\n </button>\r\n <button\r\n class=\"tab-button\"\r\n [class.active]=\"activeTab === 'multiple'\"\r\n (click)=\"onTabChange('multiple')\">\r\n Multiple Dates\r\n </button>\r\n <button\r\n class=\"tab-button\"\r\n [class.active]=\"activeTab === 'range'\"\r\n (click)=\"onTabChange('range')\">\r\n Date Range\r\n </button>\r\n </div>\r\n <!-- Single Date Calendar -->\r\n <div *ngIf=\"activeTab === 'single'\" class=\"calendar-wrapper-inline\">\r\n <bk-custom-calendar\r\n [inline]=\"true\"\r\n [dualCalendar]=\"false\"\r\n [singleDatePicker]=\"true\"\r\n [showRanges]=\"false\"\r\n [enableTimepicker]=\"false\"\r\n [showCancel]=\"false\"\r\n placeholder=\"Select a date\"\r\n (selected)=\"onSingleDateSelected($event)\">\r\n </bk-custom-calendar>\r\n </div>\r\n\r\n <!-- Multiple Dates Calendar -->\r\n <div *ngIf=\"activeTab === 'multiple'\" class=\"calendar-wrapper-inline\">\r\n <bk-custom-calendar\r\n [inline]=\"true\"\r\n [dualCalendar]=\"false\"\r\n [singleDatePicker]=\"false\"\r\n [showRanges]=\"false\"\r\n [enableTimepicker]=\"false\"\r\n [multiDateSelection]=\"true\"\r\n [showCancel]=\"false\"\r\n placeholder=\"Select multiple dates\"\r\n (selected)=\"onMultipleDatesSelected($event)\">\r\n </bk-custom-calendar>\r\n </div>\r\n\r\n <!-- Date Range Calendar -->\r\n <div *ngIf=\"activeTab === 'range'\" class=\"calendar-wrapper-inline\">\r\n <bk-custom-calendar\r\n [inline]=\"true\"\r\n [dualCalendar]=\"false\"\r\n [singleDatePicker]=\"false\"\r\n [showRanges]=\"false\"\r\n [enableTimepicker]=\"false\"\r\n [showCancel]=\"false\"\r\n placeholder=\"Select date range\"\r\n (selected)=\"onRangeSelected($event)\">\r\n </bk-custom-calendar>\r\n </div>\r\n </div>\r\n\r\n <!-- Right Side: Time Configuration -->\r\n <div class=\"time-config-section\">\r\n <h3 class=\"time-config-title\">Time Configuration</h3>\r\n\r\n <!-- Single Date Time Configuration -->\r\n <div *ngIf=\"activeTab === 'single'\">\r\n <div *ngIf=\"singleDate\" class=\"time-config-item\">\r\n <div class=\"time-config-header\">\r\n <span class=\"date-label\">{{ formatDate(singleDate) }}</span>\r\n <label class=\"all-day-toggle\">\r\n <span class=\"toggle-label\">All Day</span>\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"singleAllDay\"\r\n (change)=\"onSingleAllDayChange()\">\r\n </label>\r\n </div>\r\n <div *ngIf=\"!singleAllDay\" class=\"time-inputs\">\r\n <bk-time-picker\r\n pickerId=\"single-start\"\r\n label=\"Start Time\"\r\n [value]=\"singleStartTime\"\r\n [position]=\"'left'\"\r\n [closePicker]=\"shouldClosePicker('single-start')\"\r\n (timeChange)=\"onSingleStartTimeChange($event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n <bk-time-picker\r\n pickerId=\"single-end\"\r\n label=\"End Time\"\r\n [value]=\"singleEndTime\"\r\n [position]=\"'right'\"\r\n [closePicker]=\"shouldClosePicker('single-end')\"\r\n (timeChange)=\"onSingleEndTimeChange($event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n <div *ngIf=\"!singleDate\" class=\"no-selection\">\r\n <p>No date selected. Select a date from the calendar.</p>\r\n </div>\r\n </div>\r\n\r\n <!-- Multiple Dates Time Configuration -->\r\n <div *ngIf=\"activeTab === 'multiple'\" class=\"time-config-list\">\r\n <div\r\n *ngFor=\"let dateConfig of multipleDates; let i = index\"\r\n class=\"time-config-item\">\r\n <div class=\"time-config-header\">\r\n <span class=\"date-label\">{{ formatDate(dateConfig.date) }}</span>\r\n <label class=\"all-day-toggle\">\r\n <span class=\"toggle-label\">All Day</span>\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dateConfig.allDay\"\r\n (change)=\"onMultipleDateAllDayChange(i)\">\r\n </label>\r\n </div>\r\n <div *ngIf=\"!dateConfig.allDay\" class=\"time-inputs\">\r\n <bk-time-picker\r\n [pickerId]=\"'multiple-' + i + '-start'\"\r\n label=\"Start Time\"\r\n [value]=\"dateConfig.startTime\"\r\n [position]=\"'left'\"\r\n [closePicker]=\"shouldClosePicker('multiple-' + i + '-start')\"\r\n (timeChange)=\"onMultipleDateStartTimeChange(i, $event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n <bk-time-picker\r\n [pickerId]=\"'multiple-' + i + '-end'\"\r\n label=\"End Time\"\r\n [value]=\"dateConfig.endTime\"\r\n [position]=\"'right'\"\r\n [closePicker]=\"shouldClosePicker('multiple-' + i + '-end')\"\r\n (timeChange)=\"onMultipleDateEndTimeChange(i, $event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n <div *ngIf=\"multipleDates.length === 0\" class=\"no-selection\">\r\n <p>No dates selected. Select dates from the calendar.</p>\r\n </div>\r\n </div>\r\n\r\n <!-- Date Range Time Configuration -->\r\n <div *ngIf=\"activeTab === 'range' && rangeStartDate && rangeEndDate\" class=\"time-config-item\">\r\n <div class=\"time-config-header\">\r\n <span class=\"date-label\">{{ formatDate(rangeStartDate) }} - {{ formatDate(rangeEndDate) }}</span>\r\n <label class=\"all-day-toggle\">\r\n <span class=\"toggle-label\">All Day</span>\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"rangeAllDay\"\r\n (change)=\"onRangeAllDayChange()\">\r\n </label>\r\n </div>\r\n <div *ngIf=\"!rangeAllDay\" class=\"time-inputs\">\r\n <bk-time-picker\r\n pickerId=\"range-start\"\r\n label=\"Start Time\"\r\n [value]=\"rangeStartTime\"\r\n [position]=\"'left'\"\r\n [closePicker]=\"shouldClosePicker('range-start')\"\r\n (timeChange)=\"onRangeStartTimeChange($event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n <bk-time-picker\r\n pickerId=\"range-end\"\r\n label=\"End Time\"\r\n [value]=\"rangeEndTime\"\r\n [position]=\"'right'\"\r\n [closePicker]=\"shouldClosePicker('range-end')\"\r\n (timeChange)=\"onRangeEndTimeChange($event)\"\r\n (pickerOpened)=\"onTimePickerOpened($event)\"\r\n (pickerClosed)=\"onTimePickerClosed($event)\">\r\n </bk-time-picker>\r\n </div>\r\n </div>\r\n <div *ngIf=\"activeTab === 'range' && (!rangeStartDate || !rangeEndDate)\" class=\"no-selection\">\r\n <p>No date range selected. Select a date range from the calendar.</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Action Buttons -->\r\n <div class=\"action-buttons\">\r\n <button class=\"btn-clear\" (click)=\"clear()\">Clear</button>\r\n <button class=\"btn-apply\" (click)=\"apply()\">Apply</button>\r\n </div>\r\n</div>\r\n\r\n", styles: [".scheduled-date-picker-container{font-family:Inter,sans-serif;background:#fff;border-radius:12px;padding:0;box-shadow:0 2px 8px #0000001a;overflow:hidden;width:100%;max-width:100%;box-sizing:border-box}.scheduled-header{padding:24px 24px 16px;border-bottom:1px solid #e5e7eb;background:#fff}.scheduled-title{font-size:18px;font-weight:500;line-height:26px;color:#111827;letter-spacing:-.28px;margin:0 0 16px}.tabs{display:flex;margin-bottom:16px;border-radius:6px;padding:3px;background-color:#54578e12}.tab-button{padding:5px 11px;border:none;background:transparent;color:#6b7080;font-size:11px;font-weight:500;cursor:pointer;border:1px solid transparent;transition:all .2s;font-family:Inter,sans-serif;flex:1;border-radius:4px}.tab-button.active{color:#15191e;border-color:#42578a26;background:#fff}.scheduled-content{display:flex;gap:0;align-items:stretch}.calendar-section{flex:0 0 55%;max-width:55%;padding:12px;border-right:1px solid #e5e7eb;background:#fff;box-sizing:border-box}.calendar-wrapper-inline{width:100%}.calendar-wrapper-inline app-custom-calendar{width:100%}.time-config-section{flex:0 0 45%;max-width:45%;padding:12px;background:#fff;overflow-y:auto;max-height:600px;box-sizing:border-box}.time-config-title{font-size:16px;font-weight:600;color:#111827;margin:17px 0 14px}.time-config-item{padding:14px;border:1px solid #e5e7eb;border-radius:8px;background:#fff}.time-config-header{display:flex;justify-content:space-between;align-items:center}.date-label{font-size:12px;font-weight:500;color:#15191e;letter-spacing:-.28px}.all-day-toggle{display:flex;align-items:center;gap:5px;cursor:pointer;-webkit-user-select:none;user-select:none}.all-day-toggle input[type=checkbox]{width:28px;height:16px;appearance:none;background:#bbbdc5;border-radius:10px;position:relative;cursor:pointer;transition:background .2s;margin:0}.all-day-toggle input[type=checkbox]:checked{background:#22973f}.all-day-toggle input[type=checkbox]:before{content:\"\";position:absolute;width:12px;height:12px;border-radius:50%;background:#fff;top:1.5px;left:2.5px;transition:transform .2s;box-shadow:0 1px 3px #0003}.all-day-toggle input[type=checkbox]:checked:before{transform:translate(12px)}.toggle-label{font-size:12px;font-weight:500;color:#111827}.all-day-toggle input[type=checkbox]:checked+.toggle-label{color:#111827}.time-inputs{display:flex;gap:14px;margin-top:12px}.time-config-list{display:flex;flex-direction:column;gap:14px;max-height:350px;overflow-y:auto;padding-right:4px}.time-config-list::-webkit-scrollbar{width:6px;height:6px}.time-config-list::-webkit-scrollbar-track{background:#f1f1f1;border-radius:3px}.time-config-list::-webkit-scrollbar-thumb{background:#b4b4b4;border-radius:3px}.time-config-list::-webkit-scrollbar-thumb:hover{background:#9b9b9b}.no-selection{padding:24px;text-align:center;color:#9ca3af;font-size:14px}.action-buttons{display:flex;justify-content:flex-end;gap:12px;padding:12px;border-top:1px solid #e5e7eb;background:#fff}.btn-clear,.btn-apply{padding:10px 20px;border:none;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;font-family:Inter,sans-serif;min-width:80px}.btn-clear{background:#fff;color:#6b7280;border:1px solid #d1d5db}.btn-clear:hover{background:#f9fafb;border-color:#9ca3af}.btn-apply{background:#111827;color:#fff}.btn-apply:hover{background:#374151}@media (max-width: 1200px){.calendar-section{flex:0 0 52%;max-width:52%}.time-config-section{flex:0 0 48%;max-width:48%}}@media (max-width: 1024px){.scheduled-content{flex-direction:column}.calendar-section{flex:1 1 auto;max-width:100%;border-right:none;border-bottom:1px solid #e5e7eb}.time-config-section{flex:1 1 auto;max-width:100%;max-height:none}.time-config-list{max-height:320px}}@media (max-width: 768px){.scheduled-date-picker-container{border-radius:0}.scheduled-header{padding:16px}.calendar-section,.time-config-section{padding:12px 16px}.tabs{overflow-x:auto}.tab-button{white-space:nowrap;font-size:12px;padding:6px 10px}.time-inputs{flex-direction:column}.time-config-item{padding:12px}.action-buttons{padding:10px}}@media (max-width: 480px){.scheduled-title{font-size:16px}.time-config-title{font-size:14px}.date-label{font-size:11px}.time-config-list{max-height:260px}.btn-clear,.btn-apply{padding:8px 14px;font-size:13px}}\n"] }]
|
|
2242
|
+
}], propDecorators: { timeFormat: [{
|
|
2243
|
+
type: Input
|
|
2244
|
+
}], enableSeconds: [{
|
|
2245
|
+
type: Input
|
|
2246
|
+
}], scheduled: [{
|
|
2247
|
+
type: Output
|
|
2248
|
+
}], cleared: [{
|
|
2249
|
+
type: Output
|
|
2250
|
+
}] } });
|
|
2251
|
+
|
|
2252
|
+
/**
|
|
2253
|
+
* Optional NgModule wrapper for projects that prefer module-based usage.
|
|
2254
|
+
*
|
|
2255
|
+
* Note:
|
|
2256
|
+
* - The components themselves are standalone, so you can also import them
|
|
2257
|
+
* directly into any standalone component without using this module.
|
|
2258
|
+
* - This module is mainly for:
|
|
2259
|
+
* - Existing apps that still use feature modules
|
|
2260
|
+
* - Easier "plug-and-play" integration: import CalendarModule once and use
|
|
2261
|
+
* the three exported components anywhere in your templates.
|
|
2262
|
+
*/
|
|
2263
|
+
class CalendarModule {
|
|
2264
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CalendarModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
2265
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: CalendarModule, imports: [CommonModule,
|
|
2266
|
+
BkCustomCalendar,
|
|
2267
|
+
BkScheduledDatePicker,
|
|
2268
|
+
BkTimePicker], exports: [BkCustomCalendar,
|
|
2269
|
+
BkScheduledDatePicker,
|
|
2270
|
+
BkTimePicker] });
|
|
2271
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CalendarModule, imports: [CommonModule,
|
|
2272
|
+
BkCustomCalendar,
|
|
2273
|
+
BkScheduledDatePicker,
|
|
2274
|
+
BkTimePicker] });
|
|
2275
|
+
}
|
|
2276
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CalendarModule, decorators: [{
|
|
2277
|
+
type: NgModule,
|
|
2278
|
+
args: [{
|
|
2279
|
+
imports: [
|
|
2280
|
+
CommonModule,
|
|
2281
|
+
BkCustomCalendar,
|
|
2282
|
+
BkScheduledDatePicker,
|
|
2283
|
+
BkTimePicker
|
|
2284
|
+
],
|
|
2285
|
+
exports: [
|
|
2286
|
+
BkCustomCalendar,
|
|
2287
|
+
BkScheduledDatePicker,
|
|
2288
|
+
BkTimePicker
|
|
2289
|
+
]
|
|
2290
|
+
}]
|
|
2291
|
+
}] });
|
|
2292
|
+
|
|
2293
|
+
class BkToggle {
|
|
2294
|
+
label = '';
|
|
2295
|
+
disabled = false;
|
|
2296
|
+
toggleClass = 'toggle-md';
|
|
2297
|
+
change = new EventEmitter();
|
|
2298
|
+
isChecked = false;
|
|
2299
|
+
// CVA callbacks (placeholders)
|
|
2300
|
+
onChange = (_) => { };
|
|
2301
|
+
onTouched = () => { };
|
|
2302
|
+
toggle() {
|
|
2303
|
+
if (this.disabled)
|
|
2304
|
+
return;
|
|
2305
|
+
this.isChecked = !this.isChecked;
|
|
2306
|
+
this.onChange(this.isChecked); // Notify Forms API
|
|
2307
|
+
this.onTouched(); // Notify Validation API
|
|
2308
|
+
this.change.emit(this.isChecked); // Notify standard event listeners
|
|
2309
|
+
}
|
|
2310
|
+
// Called by Angular to write value to the view
|
|
2311
|
+
writeValue(value) {
|
|
2312
|
+
this.isChecked = value;
|
|
2313
|
+
}
|
|
2314
|
+
// Called by Angular to register the function to call when changed
|
|
2315
|
+
registerOnChange(fn) {
|
|
2316
|
+
this.onChange = fn;
|
|
2317
|
+
}
|
|
2318
|
+
// Called by Angular to register the function to call when touched
|
|
2319
|
+
registerOnTouched(fn) {
|
|
2320
|
+
this.onTouched = fn;
|
|
2321
|
+
}
|
|
2322
|
+
// Called by Angular when the disabled state changes
|
|
2323
|
+
setDisabledState(isDisabled) {
|
|
2324
|
+
this.disabled = isDisabled;
|
|
2325
|
+
}
|
|
2326
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkToggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2327
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkToggle, isStandalone: true, selector: "bk-toggle", inputs: { label: "label", disabled: "disabled", toggleClass: "toggleClass" }, outputs: { change: "change" }, providers: [
|
|
2328
|
+
{
|
|
2329
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2330
|
+
useExisting: forwardRef(() => BkToggle),
|
|
2331
|
+
multi: true
|
|
2332
|
+
}
|
|
2333
|
+
], ngImport: i0, template: "<div class=\"inline-flex items-center gap-2 cursor-pointer\" (click)=\"toggle()\">\r\n <button\r\n type=\"button\"\r\n role=\"switch\"\r\n [attr.aria-checked]=\"isChecked\"\r\n [disabled]=\"disabled\"\r\n class=\"toggle-base\"\r\n [ngClass]=\"toggleClass\"\r\n [class.toggle-on]=\"isChecked\"\r\n [class.toggle-off]=\"!isChecked\"\r\n [class.toggle-disabled]=\"disabled\"\r\n >\r\n <span\r\n class=\"toggle-knob\"\r\n [class.knob-on]=\"isChecked\"\r\n [class.knob-off]=\"!isChecked\"\r\n ></span>\r\n </button>\r\n @if (label){\r\n <span class=\"text-sm font-medium text-[#1B223A] select-none\" [class.opacity-70]=\"disabled\">\r\n {{ label }}\r\n </span>\r\n }\r\n</div>\r\n", styles: [".toggle-base{@apply relative inline-flex items-center rounded-full transition-colors duration-200 ease-in-out border-2 border-transparent;@apply focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1336EF] focus-visible:ring-offset-2;}.toggle-off{@apply bg-[#BBBDC5] hover:bg-[#A1A3AE];}.toggle-on{@apply bg-[#22973F] hover:bg-[#1E7735];}.toggle-disabled{@apply bg-[#D6D7DC] hover:bg-[#D6D7DC] cursor-not-allowed;}.toggle-knob{@apply pointer-events-none inline-block transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out;}.toggle-sm{@apply w-7;}.toggle-sm .toggle-knob{@apply h-3 w-3;}.toggle-sm .knob-on{@apply translate-x-3;}.toggle-sm .knob-off{@apply translate-x-0;}.toggle-md{@apply w-9;}.toggle-md .toggle-knob{@apply h-4 w-4;}.toggle-md .knob-on{@apply translate-x-4;}.toggle-md .knob-off{@apply translate-x-0;}.toggle-lg{@apply w-11;}.toggle-lg .toggle-knob{@apply h-5 w-5;}.toggle-lg .knob-on{@apply translate-x-5;}.toggle-lg .knob-off{@apply translate-x-0;}.simulate-hover.toggle-off{@apply bg-[#A1A3AE];}.simulate-hover.toggle-on{@apply bg-[#1E7735];}.simulate-focus{@apply ring-2 ring-[#1336EF] ring-offset-2;}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
2334
|
+
}
|
|
2335
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkToggle, decorators: [{
|
|
2336
|
+
type: Component,
|
|
2337
|
+
args: [{ selector: 'bk-toggle', standalone: true, imports: [CommonModule], encapsulation: ViewEncapsulation.None, providers: [
|
|
2338
|
+
{
|
|
2339
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2340
|
+
useExisting: forwardRef(() => BkToggle),
|
|
2341
|
+
multi: true
|
|
2342
|
+
}
|
|
2343
|
+
], template: "<div class=\"inline-flex items-center gap-2 cursor-pointer\" (click)=\"toggle()\">\r\n <button\r\n type=\"button\"\r\n role=\"switch\"\r\n [attr.aria-checked]=\"isChecked\"\r\n [disabled]=\"disabled\"\r\n class=\"toggle-base\"\r\n [ngClass]=\"toggleClass\"\r\n [class.toggle-on]=\"isChecked\"\r\n [class.toggle-off]=\"!isChecked\"\r\n [class.toggle-disabled]=\"disabled\"\r\n >\r\n <span\r\n class=\"toggle-knob\"\r\n [class.knob-on]=\"isChecked\"\r\n [class.knob-off]=\"!isChecked\"\r\n ></span>\r\n </button>\r\n @if (label){\r\n <span class=\"text-sm font-medium text-[#1B223A] select-none\" [class.opacity-70]=\"disabled\">\r\n {{ label }}\r\n </span>\r\n }\r\n</div>\r\n", styles: [".toggle-base{@apply relative inline-flex items-center rounded-full transition-colors duration-200 ease-in-out border-2 border-transparent;@apply focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1336EF] focus-visible:ring-offset-2;}.toggle-off{@apply bg-[#BBBDC5] hover:bg-[#A1A3AE];}.toggle-on{@apply bg-[#22973F] hover:bg-[#1E7735];}.toggle-disabled{@apply bg-[#D6D7DC] hover:bg-[#D6D7DC] cursor-not-allowed;}.toggle-knob{@apply pointer-events-none inline-block transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out;}.toggle-sm{@apply w-7;}.toggle-sm .toggle-knob{@apply h-3 w-3;}.toggle-sm .knob-on{@apply translate-x-3;}.toggle-sm .knob-off{@apply translate-x-0;}.toggle-md{@apply w-9;}.toggle-md .toggle-knob{@apply h-4 w-4;}.toggle-md .knob-on{@apply translate-x-4;}.toggle-md .knob-off{@apply translate-x-0;}.toggle-lg{@apply w-11;}.toggle-lg .toggle-knob{@apply h-5 w-5;}.toggle-lg .knob-on{@apply translate-x-5;}.toggle-lg .knob-off{@apply translate-x-0;}.simulate-hover.toggle-off{@apply bg-[#A1A3AE];}.simulate-hover.toggle-on{@apply bg-[#1E7735];}.simulate-focus{@apply ring-2 ring-[#1336EF] ring-offset-2;}\n"] }]
|
|
2344
|
+
}], propDecorators: { label: [{
|
|
2345
|
+
type: Input
|
|
2346
|
+
}], disabled: [{
|
|
2347
|
+
type: Input
|
|
2348
|
+
}], toggleClass: [{
|
|
2349
|
+
type: Input
|
|
2350
|
+
}], change: [{
|
|
2351
|
+
type: Output
|
|
2352
|
+
}] } });
|
|
2353
|
+
|
|
2354
|
+
class BkCheckbox {
|
|
2355
|
+
checkboxClass = '';
|
|
2356
|
+
label = '';
|
|
2357
|
+
labelClass = '';
|
|
2358
|
+
disabled = false;
|
|
2359
|
+
change = new EventEmitter();
|
|
2360
|
+
// This is the value bound via ngModel
|
|
2361
|
+
isChecked = false;
|
|
2362
|
+
// ControlValueAccessor callbacks
|
|
2363
|
+
onChange = (_) => { };
|
|
2364
|
+
onTouched = () => { };
|
|
2365
|
+
// Toggle function for click / keyboard
|
|
2366
|
+
toggle() {
|
|
2367
|
+
if (this.disabled)
|
|
2368
|
+
return;
|
|
2369
|
+
this.isChecked = !this.isChecked;
|
|
2370
|
+
// Update ngModel value
|
|
2371
|
+
this.onChange(this.isChecked);
|
|
2372
|
+
this.onTouched();
|
|
2373
|
+
// Emit the change event
|
|
2374
|
+
this.change.emit(this.isChecked);
|
|
2375
|
+
}
|
|
2376
|
+
/** ------------------ ControlValueAccessor methods ------------------ */
|
|
2377
|
+
writeValue(value) {
|
|
2378
|
+
this.isChecked = value ?? false; // handle null/undefined safely
|
|
2379
|
+
}
|
|
2380
|
+
registerOnChange(fn) {
|
|
2381
|
+
this.onChange = fn;
|
|
2382
|
+
}
|
|
2383
|
+
registerOnTouched(fn) {
|
|
2384
|
+
this.onTouched = fn;
|
|
2385
|
+
}
|
|
2386
|
+
setDisabledState(isDisabled) {
|
|
2387
|
+
this.disabled = isDisabled;
|
|
2388
|
+
}
|
|
2389
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkCheckbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2390
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkCheckbox, isStandalone: true, selector: "bk-checkbox", inputs: { checkboxClass: "checkboxClass", label: "label", labelClass: "labelClass", disabled: "disabled" }, outputs: { change: "change" }, providers: [
|
|
2391
|
+
{
|
|
2392
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2393
|
+
useExisting: forwardRef(() => BkCheckbox),
|
|
2394
|
+
multi: true
|
|
2395
|
+
}
|
|
2396
|
+
], ngImport: i0, template: "<div\r\n class=\"inline-flex items-center gap-2 cursor-pointer group outline-none\"\r\n (click)=\"toggle()\"\r\n (keydown.enter)=\"toggle()\"\r\n (keydown.space)=\"$event.preventDefault(); toggle()\"\r\n tabindex=\"0\"\r\n [attr.aria-disabled]=\"disabled\">\r\n <div\r\n class=\"relative flex items-center justify-center border-2 transition-all duration-200 ease-in-out rounded group-focus-visible:ring-2 group-focus-visible:ring-blue-600 group-focus-visible:ring-offset-2 checkbox\"\r\n [ngClass]=\"[\r\n checkboxClass,\r\n isChecked && !disabled ? 'bg-black border-black' : '',\r\n !isChecked && !disabled ? 'bg-white border-gray-300 group-hover:border-gray-400' : '',\r\n disabled && isChecked ? 'bg-gray-300 border-gray-300' : '',\r\n disabled && !isChecked ? 'bg-gray-100 border-gray-200' : '',\r\n disabled ? 'cursor-not-allowed' : ''\r\n ]\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"3.5\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n class=\"text-white pointer-events-none transition-opacity duration-200\"\r\n [class.opacity-0]=\"!isChecked\"\r\n [class.opacity-100]=\"isChecked\"\r\n >\r\n <polyline points=\"20 6 9 17 4 12\"></polyline>\r\n </svg>\r\n </div>\r\n@if(label){\r\n <span\r\n [ngClass]=\"disabled ? 'text-gray-400' : ''\"\r\n class=\"font-medium text-xs text-[#1B223A] select-none {{labelClass}}\"\r\n >\r\n {{ label }}\r\n </span>\r\n}\r\n</div>\r\n", styles: [".checkbox.xsm{@apply size-[14px];}.checkbox.sm{@apply size-[16px];}.checkbox.md{@apply size-[18px];}.checkbox.lg{@apply size-[20px];}.checkbox.xsm svg{@apply size-[10.5px];}.checkbox.sm svg{@apply size-[12px];}.checkbox.md svg{@apply size-[13.5px];}.checkbox.lg svg{@apply size-[14px];}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
2397
|
+
}
|
|
2398
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkCheckbox, decorators: [{
|
|
2399
|
+
type: Component,
|
|
2400
|
+
args: [{ selector: 'bk-checkbox', standalone: true, imports: [CommonModule], encapsulation: ViewEncapsulation.None, providers: [
|
|
2401
|
+
{
|
|
2402
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2403
|
+
useExisting: forwardRef(() => BkCheckbox),
|
|
2404
|
+
multi: true
|
|
2405
|
+
}
|
|
2406
|
+
], template: "<div\r\n class=\"inline-flex items-center gap-2 cursor-pointer group outline-none\"\r\n (click)=\"toggle()\"\r\n (keydown.enter)=\"toggle()\"\r\n (keydown.space)=\"$event.preventDefault(); toggle()\"\r\n tabindex=\"0\"\r\n [attr.aria-disabled]=\"disabled\">\r\n <div\r\n class=\"relative flex items-center justify-center border-2 transition-all duration-200 ease-in-out rounded group-focus-visible:ring-2 group-focus-visible:ring-blue-600 group-focus-visible:ring-offset-2 checkbox\"\r\n [ngClass]=\"[\r\n checkboxClass,\r\n isChecked && !disabled ? 'bg-black border-black' : '',\r\n !isChecked && !disabled ? 'bg-white border-gray-300 group-hover:border-gray-400' : '',\r\n disabled && isChecked ? 'bg-gray-300 border-gray-300' : '',\r\n disabled && !isChecked ? 'bg-gray-100 border-gray-200' : '',\r\n disabled ? 'cursor-not-allowed' : ''\r\n ]\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"3.5\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n class=\"text-white pointer-events-none transition-opacity duration-200\"\r\n [class.opacity-0]=\"!isChecked\"\r\n [class.opacity-100]=\"isChecked\"\r\n >\r\n <polyline points=\"20 6 9 17 4 12\"></polyline>\r\n </svg>\r\n </div>\r\n@if(label){\r\n <span\r\n [ngClass]=\"disabled ? 'text-gray-400' : ''\"\r\n class=\"font-medium text-xs text-[#1B223A] select-none {{labelClass}}\"\r\n >\r\n {{ label }}\r\n </span>\r\n}\r\n</div>\r\n", styles: [".checkbox.xsm{@apply size-[14px];}.checkbox.sm{@apply size-[16px];}.checkbox.md{@apply size-[18px];}.checkbox.lg{@apply size-[20px];}.checkbox.xsm svg{@apply size-[10.5px];}.checkbox.sm svg{@apply size-[12px];}.checkbox.md svg{@apply size-[13.5px];}.checkbox.lg svg{@apply size-[14px];}\n"] }]
|
|
2407
|
+
}], propDecorators: { checkboxClass: [{
|
|
2408
|
+
type: Input
|
|
2409
|
+
}], label: [{
|
|
2410
|
+
type: Input
|
|
2411
|
+
}], labelClass: [{
|
|
2412
|
+
type: Input
|
|
2413
|
+
}], disabled: [{
|
|
2414
|
+
type: Input
|
|
2415
|
+
}], change: [{
|
|
2416
|
+
type: Output
|
|
2417
|
+
}] } });
|
|
2418
|
+
|
|
2419
|
+
class BkRadioButton {
|
|
2420
|
+
radioClass = '';
|
|
2421
|
+
label = '';
|
|
2422
|
+
labelClass = '';
|
|
2423
|
+
value;
|
|
2424
|
+
disabled = false;
|
|
2425
|
+
variant = 'dot';
|
|
2426
|
+
change = new EventEmitter();
|
|
2427
|
+
modelValue;
|
|
2428
|
+
onChange = (_) => { };
|
|
2429
|
+
onTouched = () => { };
|
|
2430
|
+
select() {
|
|
2431
|
+
if (this.disabled)
|
|
2432
|
+
return;
|
|
2433
|
+
if (this.modelValue !== this.value) {
|
|
2434
|
+
this.modelValue = this.value;
|
|
2435
|
+
this.onChange(this.value);
|
|
2436
|
+
this.onTouched();
|
|
2437
|
+
this.change.emit(this.value);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
get isChecked() {
|
|
2441
|
+
return this.modelValue === this.value;
|
|
2442
|
+
}
|
|
2443
|
+
writeValue(value) {
|
|
2444
|
+
this.modelValue = value;
|
|
2445
|
+
}
|
|
2446
|
+
registerOnChange(fn) {
|
|
2447
|
+
this.onChange = fn;
|
|
2448
|
+
}
|
|
2449
|
+
registerOnTouched(fn) {
|
|
2450
|
+
this.onTouched = fn;
|
|
2451
|
+
}
|
|
2452
|
+
setDisabledState(isDisabled) {
|
|
2453
|
+
this.disabled = isDisabled;
|
|
2454
|
+
}
|
|
2455
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkRadioButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2456
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkRadioButton, isStandalone: true, selector: "bk-radio-button", inputs: { radioClass: "radioClass", label: "label", labelClass: "labelClass", value: "value", disabled: "disabled", variant: "variant" }, outputs: { change: "change" }, providers: [
|
|
2457
|
+
{
|
|
2458
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2459
|
+
useExisting: forwardRef(() => BkRadioButton),
|
|
2460
|
+
multi: true,
|
|
2461
|
+
},
|
|
2462
|
+
], ngImport: i0, template: "<div\r\n class=\"inline-flex items-center gap-2 cursor-pointer group outline-none\"\r\n (click)=\"select()\"\r\n (keydown.enter)=\"select()\"\r\n (keydown.space)=\"$event.preventDefault(); select()\"\r\n tabindex=\"0\"\r\n [attr.aria-disabled]=\"disabled\">\r\n\r\n <div\r\n class=\"relative flex items-center justify-center rounded-full border-2 transition-all duration-200 flex-shrink-0 group-focus-visible:ring-2 group-focus-visible:ring-blue-600 group-focus-visible:ring-offset-2 radio\"\r\n [ngClass]=\"[\r\n radioClass,\r\n !isChecked && !disabled ? 'bg-white border-gray-300 group-hover:border-gray-400' : '',\r\n\r\n variant === 'dot' && isChecked && !disabled ? 'border-black bg-white' : '',\r\n\r\n variant === 'tick' && isChecked && !disabled ? 'bg-black border-black' : '',\r\n\r\n disabled && isChecked && variant === 'tick' ? 'bg-gray-300 border-gray-300' : '',\r\n disabled && isChecked && variant === 'dot' ? 'border-gray-300 bg-gray-50' : '',\r\n disabled && !isChecked ? 'bg-gray-100 border-gray-200' : '',\r\n disabled ? 'cursor-not-allowed' : ''\r\n ]\"\r\n >\r\n@if(variant === 'dot'){\r\n <span\r\n class=\"rounded-full bg-black transition-transform duration-200 transform dot\"\r\n [class.scale-0]=\"!isChecked\"\r\n [class.scale-100]=\"isChecked\"\r\n [class.bg-gray-400]=\"disabled\"\r\n ></span>\r\n}\r\n @if (variant === 'tick'){\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"3.5\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n class=\"text-white pointer-events-none transition-opacity duration-200 tick\"\r\n [class.opacity-0]=\"!isChecked\"\r\n [class.opacity-100]=\"isChecked\"\r\n\r\n >\r\n <polyline points=\"20 6 9 17 4 12\"></polyline>\r\n </svg>\r\n }\r\n </div>\r\n\r\n @if(label) {\r\n <span class=\"font-medium text-xs text-[#1B223A] select-none {{labelClass}}\"\r\n [ngClass]=\"disabled ? 'text-gray-400' : ''\">\r\n {{ label }}\r\n </span>\r\n }\r\n</div>\r\n", styles: [".radio.xsm{@apply size-[14px];}.radio.sm{@apply size-[16px];}.radio.md{@apply size-[18px];}.radio.lg{@apply size-[19px];}.radio.xsm .dot{@apply size-[6px];}.radio.sm .dot{@apply size-[8px];}.radio.md .dot{@apply size-[10px];}.radio.lg .dot{@apply size-[11px];}.radio.xsm .tick{@apply size-[9px];}.radio.sm .tick{@apply size-[10px];}.radio.md .tick{@apply size-[12px];}.radio.lg .tick{@apply size-[14px];}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }], encapsulation: i0.ViewEncapsulation.None });
|
|
2463
|
+
}
|
|
2464
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkRadioButton, decorators: [{
|
|
2465
|
+
type: Component,
|
|
2466
|
+
args: [{ selector: 'bk-radio-button', standalone: true, imports: [CommonModule, FormsModule], encapsulation: ViewEncapsulation.None, providers: [
|
|
2467
|
+
{
|
|
2468
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2469
|
+
useExisting: forwardRef(() => BkRadioButton),
|
|
2470
|
+
multi: true,
|
|
2471
|
+
},
|
|
2472
|
+
], template: "<div\r\n class=\"inline-flex items-center gap-2 cursor-pointer group outline-none\"\r\n (click)=\"select()\"\r\n (keydown.enter)=\"select()\"\r\n (keydown.space)=\"$event.preventDefault(); select()\"\r\n tabindex=\"0\"\r\n [attr.aria-disabled]=\"disabled\">\r\n\r\n <div\r\n class=\"relative flex items-center justify-center rounded-full border-2 transition-all duration-200 flex-shrink-0 group-focus-visible:ring-2 group-focus-visible:ring-blue-600 group-focus-visible:ring-offset-2 radio\"\r\n [ngClass]=\"[\r\n radioClass,\r\n !isChecked && !disabled ? 'bg-white border-gray-300 group-hover:border-gray-400' : '',\r\n\r\n variant === 'dot' && isChecked && !disabled ? 'border-black bg-white' : '',\r\n\r\n variant === 'tick' && isChecked && !disabled ? 'bg-black border-black' : '',\r\n\r\n disabled && isChecked && variant === 'tick' ? 'bg-gray-300 border-gray-300' : '',\r\n disabled && isChecked && variant === 'dot' ? 'border-gray-300 bg-gray-50' : '',\r\n disabled && !isChecked ? 'bg-gray-100 border-gray-200' : '',\r\n disabled ? 'cursor-not-allowed' : ''\r\n ]\"\r\n >\r\n@if(variant === 'dot'){\r\n <span\r\n class=\"rounded-full bg-black transition-transform duration-200 transform dot\"\r\n [class.scale-0]=\"!isChecked\"\r\n [class.scale-100]=\"isChecked\"\r\n [class.bg-gray-400]=\"disabled\"\r\n ></span>\r\n}\r\n @if (variant === 'tick'){\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"3.5\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n class=\"text-white pointer-events-none transition-opacity duration-200 tick\"\r\n [class.opacity-0]=\"!isChecked\"\r\n [class.opacity-100]=\"isChecked\"\r\n\r\n >\r\n <polyline points=\"20 6 9 17 4 12\"></polyline>\r\n </svg>\r\n }\r\n </div>\r\n\r\n @if(label) {\r\n <span class=\"font-medium text-xs text-[#1B223A] select-none {{labelClass}}\"\r\n [ngClass]=\"disabled ? 'text-gray-400' : ''\">\r\n {{ label }}\r\n </span>\r\n }\r\n</div>\r\n", styles: [".radio.xsm{@apply size-[14px];}.radio.sm{@apply size-[16px];}.radio.md{@apply size-[18px];}.radio.lg{@apply size-[19px];}.radio.xsm .dot{@apply size-[6px];}.radio.sm .dot{@apply size-[8px];}.radio.md .dot{@apply size-[10px];}.radio.lg .dot{@apply size-[11px];}.radio.xsm .tick{@apply size-[9px];}.radio.sm .tick{@apply size-[10px];}.radio.md .tick{@apply size-[12px];}.radio.lg .tick{@apply size-[14px];}\n"] }]
|
|
2473
|
+
}], propDecorators: { radioClass: [{
|
|
2474
|
+
type: Input
|
|
2475
|
+
}], label: [{
|
|
2476
|
+
type: Input
|
|
2477
|
+
}], labelClass: [{
|
|
2478
|
+
type: Input
|
|
2479
|
+
}], value: [{
|
|
2480
|
+
type: Input
|
|
2481
|
+
}], disabled: [{
|
|
2482
|
+
type: Input
|
|
2483
|
+
}], variant: [{
|
|
2484
|
+
type: Input
|
|
2485
|
+
}], change: [{
|
|
2486
|
+
type: Output
|
|
2487
|
+
}] } });
|
|
2488
|
+
|
|
2489
|
+
class BkPill {
|
|
2490
|
+
label = '';
|
|
2491
|
+
variant = 'Light';
|
|
2492
|
+
color = 'Gray';
|
|
2493
|
+
size = 'md';
|
|
2494
|
+
dot = 'none';
|
|
2495
|
+
removable = false;
|
|
2496
|
+
customClass = '';
|
|
2497
|
+
clicked = new EventEmitter();
|
|
2498
|
+
get containerClasses() {
|
|
2499
|
+
// 1. Size Class
|
|
2500
|
+
const sizeClass = `pill-${this.size}`;
|
|
2501
|
+
// 2. Color/Variant Class (Dynamic Generation)
|
|
2502
|
+
const styleClass = `${this.color}-${this.variant}`;
|
|
2503
|
+
// 3. customClasses
|
|
2504
|
+
const customClass = `${this.customClass}`;
|
|
2505
|
+
return `pill ${sizeClass} ${styleClass} ${customClass}`;
|
|
2506
|
+
}
|
|
2507
|
+
onRemove(e) {
|
|
2508
|
+
e.stopPropagation();
|
|
2509
|
+
this.clicked.emit(this.label);
|
|
2510
|
+
}
|
|
2511
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkPill, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2512
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkPill, isStandalone: true, selector: "bk-pill", inputs: { label: "label", variant: "variant", color: "color", size: "size", dot: "dot", removable: "removable", customClass: "customClass" }, outputs: { clicked: "clicked" }, ngImport: i0, template: "<span [className]=\"containerClasses\">\r\n\r\n @if (dot === 'left') {\r\n <span class=\"dot\"></span>\r\n }\r\n\r\n <span>{{ label }}</span>\r\n\r\n @if (dot === 'right') {\r\n <span class=\"dot\"></span>\r\n }\r\n\r\n @if (removable) {\r\n <button\r\n (click)=\"onRemove($event)\"\r\n class=\"pill-close \"> <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\r\n </svg>\r\n</button>\r\n }\r\n</span>\r\n", styles: [".pill{@apply inline-flex items-center justify-center font-medium border transition-colors duration-200 cursor-default gap-1.5 rounded-full;}.pill-xsm{@apply px-2 py-0.5 text-[10px] leading-[14px];}.pill-sm{@apply px-2 py-0.5 text-xs leading-[18px];}.pill-md{@apply px-2 py-0.5 text-sm;}.pill-lg{@apply px-3 py-1 text-sm;}.dot{@apply rounded-full size-1.5 flex-shrink-0 flex-grow-0 bg-current;}.pill-close{@apply flex items-center justify-center rounded-full text-inherit transition-colors;}.pill-xsm .pill-close,.pill-sm .pill-close{@apply w-3 h-3;}.pill-md .pill-close,.pill-lg .pill-close{@apply w-3.5 h-3.5;}.Gray-Solid{@apply bg-[#6B7080] border-[#6B7080] text-white;}.Primary-Solid{@apply bg-[#294FFF] border-[#294FFF] text-white;}.Error-Solid{@apply bg-[#FA727A] border-[#FA727A] text-[#4C0513];}.Warning-Solid{@apply bg-[#FA9E3A] border-[#FA9E3A] text-[#461C04];}.Success-Solid{@apply bg-[#57D175] border-[#57D175] text-[#461C04];}.Success-Solid .dot{@apply bg-[#082B13];}.Purple-Solid{@apply bg-[#CE8EF2] border-[#CE8EF2] text-[#461C04];}.Cyan-Solid{@apply bg-[#3FC2F1] border-[#3FC2F1] text-[#461C04];}.Gray-Light{@apply bg-[#F4F4F6] border-transparent text-[#363C51];}.Primary-Light{@apply bg-[#E5F3FF] border-transparent text-[#1434CB];}.Error-Light{@apply bg-[#FFF1F1] border-transparent text-[#CB1432];}.Warning-Light{@apply bg-[#FFEED7] border-transparent text-[#A04C02];}.Success-Light{@apply bg-[#F1FCF3] border-transparent text-[#1E7735];}.Purple-Light{@apply bg-[#F6EAFD] border-transparent text-[#9130C0];}.Cyan-Light{@apply bg-[#F1FAFE] border-transparent text-[#096E9B];}.Gray-Outline{@apply bg-[#F4F4F6] border-[#BBBDC5] text-[#363C51];}.Primary-Outline{@apply bg-[#E5F3FF] border-[#3F71FF] text-[#1434CB];}.Error-Outline{@apply bg-[#FFF1F1] border-[#FA727A] text-[#CB1432];}.Warning-Outline{@apply bg-[#FFEED7] border-[#FBAE58] text-[#A04C02];}.Success-Outline{@apply bg-[#F1FCF3] border-[#57D175] text-[#1E7735];}.Purple-Outline{@apply bg-[#F6EAFD] border-[#CE8EF2] text-[#9130C0];}.Cyan-Outline{@apply bg-[#F1FAFE] border-[#3FC2F1] text-[#096E9B];}.Gray-Transparent{@apply bg-transparent border-[#BBBDC5] text-[#363C51];}.Primary-Transparent{@apply bg-transparent border-[#3F71FF] text-[#1434CB];}.Error-Transparent{@apply bg-transparent border-[#FA727A] text-[#CB1432];}.Warning-Transparent{@apply bg-transparent border-[#FBAE58] text-[#A04C02];}.Success-Transparent{@apply bg-transparent border-[#57D175] text-[#1E7735];}.Purple-Transparent{@apply bg-transparent border-[#CE8EF2] text-[#9130C0];}.Cyan-Transparent{@apply bg-transparent border-[#3FC2F1] text-[#096E9B];}\n"] });
|
|
2513
|
+
}
|
|
2514
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkPill, decorators: [{
|
|
2515
|
+
type: Component,
|
|
2516
|
+
args: [{ selector: 'bk-pill', standalone: true, template: "<span [className]=\"containerClasses\">\r\n\r\n @if (dot === 'left') {\r\n <span class=\"dot\"></span>\r\n }\r\n\r\n <span>{{ label }}</span>\r\n\r\n @if (dot === 'right') {\r\n <span class=\"dot\"></span>\r\n }\r\n\r\n @if (removable) {\r\n <button\r\n (click)=\"onRemove($event)\"\r\n class=\"pill-close \"> <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\r\n </svg>\r\n</button>\r\n }\r\n</span>\r\n", styles: [".pill{@apply inline-flex items-center justify-center font-medium border transition-colors duration-200 cursor-default gap-1.5 rounded-full;}.pill-xsm{@apply px-2 py-0.5 text-[10px] leading-[14px];}.pill-sm{@apply px-2 py-0.5 text-xs leading-[18px];}.pill-md{@apply px-2 py-0.5 text-sm;}.pill-lg{@apply px-3 py-1 text-sm;}.dot{@apply rounded-full size-1.5 flex-shrink-0 flex-grow-0 bg-current;}.pill-close{@apply flex items-center justify-center rounded-full text-inherit transition-colors;}.pill-xsm .pill-close,.pill-sm .pill-close{@apply w-3 h-3;}.pill-md .pill-close,.pill-lg .pill-close{@apply w-3.5 h-3.5;}.Gray-Solid{@apply bg-[#6B7080] border-[#6B7080] text-white;}.Primary-Solid{@apply bg-[#294FFF] border-[#294FFF] text-white;}.Error-Solid{@apply bg-[#FA727A] border-[#FA727A] text-[#4C0513];}.Warning-Solid{@apply bg-[#FA9E3A] border-[#FA9E3A] text-[#461C04];}.Success-Solid{@apply bg-[#57D175] border-[#57D175] text-[#461C04];}.Success-Solid .dot{@apply bg-[#082B13];}.Purple-Solid{@apply bg-[#CE8EF2] border-[#CE8EF2] text-[#461C04];}.Cyan-Solid{@apply bg-[#3FC2F1] border-[#3FC2F1] text-[#461C04];}.Gray-Light{@apply bg-[#F4F4F6] border-transparent text-[#363C51];}.Primary-Light{@apply bg-[#E5F3FF] border-transparent text-[#1434CB];}.Error-Light{@apply bg-[#FFF1F1] border-transparent text-[#CB1432];}.Warning-Light{@apply bg-[#FFEED7] border-transparent text-[#A04C02];}.Success-Light{@apply bg-[#F1FCF3] border-transparent text-[#1E7735];}.Purple-Light{@apply bg-[#F6EAFD] border-transparent text-[#9130C0];}.Cyan-Light{@apply bg-[#F1FAFE] border-transparent text-[#096E9B];}.Gray-Outline{@apply bg-[#F4F4F6] border-[#BBBDC5] text-[#363C51];}.Primary-Outline{@apply bg-[#E5F3FF] border-[#3F71FF] text-[#1434CB];}.Error-Outline{@apply bg-[#FFF1F1] border-[#FA727A] text-[#CB1432];}.Warning-Outline{@apply bg-[#FFEED7] border-[#FBAE58] text-[#A04C02];}.Success-Outline{@apply bg-[#F1FCF3] border-[#57D175] text-[#1E7735];}.Purple-Outline{@apply bg-[#F6EAFD] border-[#CE8EF2] text-[#9130C0];}.Cyan-Outline{@apply bg-[#F1FAFE] border-[#3FC2F1] text-[#096E9B];}.Gray-Transparent{@apply bg-transparent border-[#BBBDC5] text-[#363C51];}.Primary-Transparent{@apply bg-transparent border-[#3F71FF] text-[#1434CB];}.Error-Transparent{@apply bg-transparent border-[#FA727A] text-[#CB1432];}.Warning-Transparent{@apply bg-transparent border-[#FBAE58] text-[#A04C02];}.Success-Transparent{@apply bg-transparent border-[#57D175] text-[#1E7735];}.Purple-Transparent{@apply bg-transparent border-[#CE8EF2] text-[#9130C0];}.Cyan-Transparent{@apply bg-transparent border-[#3FC2F1] text-[#096E9B];}\n"] }]
|
|
2517
|
+
}], propDecorators: { label: [{
|
|
2518
|
+
type: Input
|
|
2519
|
+
}], variant: [{
|
|
2520
|
+
type: Input
|
|
2521
|
+
}], color: [{
|
|
2522
|
+
type: Input
|
|
2523
|
+
}], size: [{
|
|
2524
|
+
type: Input
|
|
2525
|
+
}], dot: [{
|
|
2526
|
+
type: Input
|
|
2527
|
+
}], removable: [{
|
|
2528
|
+
type: Input
|
|
2529
|
+
}], customClass: [{
|
|
2530
|
+
type: Input
|
|
2531
|
+
}], clicked: [{
|
|
2532
|
+
type: Output
|
|
2533
|
+
}] } });
|
|
2534
|
+
|
|
2535
|
+
class BkBadge {
|
|
2536
|
+
label = '';
|
|
2537
|
+
variant = 'Light';
|
|
2538
|
+
color = 'Gray';
|
|
2539
|
+
size = 'md';
|
|
2540
|
+
dot = 'none';
|
|
2541
|
+
removable = false;
|
|
2542
|
+
customClass = '';
|
|
2543
|
+
clicked = new EventEmitter();
|
|
2544
|
+
get containerClasses() {
|
|
2545
|
+
// 1. Size Class
|
|
2546
|
+
const sizeClass = `badge-${this.size}`;
|
|
2547
|
+
// 2. Color/Variant Class (Dynamic Generation)
|
|
2548
|
+
const styleClass = `${this.color}-${this.variant}`;
|
|
2549
|
+
// 3. customClasses
|
|
2550
|
+
const customClass = `${this.customClass}`;
|
|
2551
|
+
return `badge ${sizeClass} ${styleClass} ${customClass}`;
|
|
2552
|
+
}
|
|
2553
|
+
onRemove(e) {
|
|
2554
|
+
e.stopPropagation();
|
|
2555
|
+
this.clicked.emit(this.label);
|
|
2556
|
+
}
|
|
2557
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkBadge, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2558
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkBadge, isStandalone: true, selector: "bk-badge", inputs: { label: "label", variant: "variant", color: "color", size: "size", dot: "dot", removable: "removable", customClass: "customClass" }, outputs: { clicked: "clicked" }, ngImport: i0, template: "<span [className]=\"containerClasses\">\r\n\r\n @if (dot === 'left') {\r\n <span class=\"dot\"></span>\r\n }\r\n\r\n <span>{{ label }}</span>\r\n\r\n @if (dot === 'right') {\r\n <span class=\"dot\"></span>\r\n }\r\n\r\n @if (removable) {\r\n <button\r\n (click)=\"onRemove($event)\"\r\n class=\"badge-close\"> <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\r\n </svg>\r\n</button>\r\n }\r\n</span>\r\n", styles: [".badge{@apply inline-flex items-center justify-center font-medium border transition-colors duration-200 cursor-default gap-1.5 rounded-[4px];}.badge-xsm{@apply px-2 py-0.5 text-[10px] leading-[14px];}.badge-sm{@apply px-2 py-0.5 text-xs leading-[18px];}.badge-md{@apply px-2 py-0.5 text-sm;}.badge-lg{@apply px-3 py-1 text-sm;}.dot{@apply rounded-full size-1.5 flex-shrink-0 flex-grow-0 bg-current;}.badge-close{@apply flex items-center justify-center rounded-full text-inherit transition-colors;}.badge-xsm .badge-close,.badge-sm .badge-close{@apply w-3 h-3;}.badge-md .badge-close,.badge-lg .badge-close{@apply w-3.5 h-3.5;}.Gray-Solid{@apply bg-[#6B7080] border-[#6B7080] text-white;}.Primary-Solid{@apply bg-[#294FFF] border-[#294FFF] text-white;}.Error-Solid{@apply bg-[#FA727A] border-[#FA727A] text-[#4C0513];}.Warning-Solid{@apply bg-[#FA9E3A] border-[#FA9E3A] text-[#461C04];}.Success-Solid{@apply bg-[#57D175] border-[#57D175] text-[#461C04];}.Success-Solid .dot{@apply bg-[#082B13];}.Purple-Solid{@apply bg-[#CE8EF2] border-[#CE8EF2] text-[#461C04];}.Cyan-Solid{@apply bg-[#3FC2F1] border-[#3FC2F1] text-[#461C04];}.Gray-Light{@apply bg-[#F4F4F6] border-transparent text-[#363C51];}.Primary-Light{@apply bg-[#E5F3FF] border-transparent text-[#1434CB];}.Error-Light{@apply bg-[#FFF1F1] border-transparent text-[#CB1432];}.Warning-Light{@apply bg-[#FFEED7] border-transparent text-[#A04C02];}.Success-Light{@apply bg-[#F1FCF3] border-transparent text-[#1E7735];}.Purple-Light{@apply bg-[#F6EAFD] border-transparent text-[#9130C0];}.Cyan-Light{@apply bg-[#F1FAFE] border-transparent text-[#096E9B];}.Gray-Outline{@apply bg-[#F4F4F6] border-[#BBBDC5] text-[#363C51];}.Primary-Outline{@apply bg-[#E5F3FF] border-[#3F71FF] text-[#1434CB];}.Error-Outline{@apply bg-[#FFF1F1] border-[#FA727A] text-[#CB1432];}.Warning-Outline{@apply bg-[#FFEED7] border-[#FBAE58] text-[#A04C02];}.Success-Outline{@apply bg-[#F1FCF3] border-[#57D175] text-[#1E7735];}.Purple-Outline{@apply bg-[#F6EAFD] border-[#CE8EF2] text-[#9130C0];}.Cyan-Outline{@apply bg-[#F1FAFE] border-[#3FC2F1] text-[#096E9B];}.Gray-Transparent{@apply bg-transparent border-[#BBBDC5] text-[#363C51];}.Primary-Transparent{@apply bg-transparent border-[#3F71FF] text-[#1434CB];}.Error-Transparent{@apply bg-transparent border-[#FA727A] text-[#CB1432];}.Warning-Transparent{@apply bg-transparent border-[#FBAE58] text-[#A04C02];}.Success-Transparent{@apply bg-transparent border-[#57D175] text-[#1E7735];}.Purple-Transparent{@apply bg-transparent border-[#CE8EF2] text-[#9130C0];}.Cyan-Transparent{@apply bg-transparent border-[#3FC2F1] text-[#096E9B];}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
2559
|
+
}
|
|
2560
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkBadge, decorators: [{
|
|
2561
|
+
type: Component,
|
|
2562
|
+
args: [{ selector: 'bk-badge', standalone: true, imports: [CommonModule], template: "<span [className]=\"containerClasses\">\r\n\r\n @if (dot === 'left') {\r\n <span class=\"dot\"></span>\r\n }\r\n\r\n <span>{{ label }}</span>\r\n\r\n @if (dot === 'right') {\r\n <span class=\"dot\"></span>\r\n }\r\n\r\n @if (removable) {\r\n <button\r\n (click)=\"onRemove($event)\"\r\n class=\"badge-close\"> <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\r\n </svg>\r\n</button>\r\n }\r\n</span>\r\n", styles: [".badge{@apply inline-flex items-center justify-center font-medium border transition-colors duration-200 cursor-default gap-1.5 rounded-[4px];}.badge-xsm{@apply px-2 py-0.5 text-[10px] leading-[14px];}.badge-sm{@apply px-2 py-0.5 text-xs leading-[18px];}.badge-md{@apply px-2 py-0.5 text-sm;}.badge-lg{@apply px-3 py-1 text-sm;}.dot{@apply rounded-full size-1.5 flex-shrink-0 flex-grow-0 bg-current;}.badge-close{@apply flex items-center justify-center rounded-full text-inherit transition-colors;}.badge-xsm .badge-close,.badge-sm .badge-close{@apply w-3 h-3;}.badge-md .badge-close,.badge-lg .badge-close{@apply w-3.5 h-3.5;}.Gray-Solid{@apply bg-[#6B7080] border-[#6B7080] text-white;}.Primary-Solid{@apply bg-[#294FFF] border-[#294FFF] text-white;}.Error-Solid{@apply bg-[#FA727A] border-[#FA727A] text-[#4C0513];}.Warning-Solid{@apply bg-[#FA9E3A] border-[#FA9E3A] text-[#461C04];}.Success-Solid{@apply bg-[#57D175] border-[#57D175] text-[#461C04];}.Success-Solid .dot{@apply bg-[#082B13];}.Purple-Solid{@apply bg-[#CE8EF2] border-[#CE8EF2] text-[#461C04];}.Cyan-Solid{@apply bg-[#3FC2F1] border-[#3FC2F1] text-[#461C04];}.Gray-Light{@apply bg-[#F4F4F6] border-transparent text-[#363C51];}.Primary-Light{@apply bg-[#E5F3FF] border-transparent text-[#1434CB];}.Error-Light{@apply bg-[#FFF1F1] border-transparent text-[#CB1432];}.Warning-Light{@apply bg-[#FFEED7] border-transparent text-[#A04C02];}.Success-Light{@apply bg-[#F1FCF3] border-transparent text-[#1E7735];}.Purple-Light{@apply bg-[#F6EAFD] border-transparent text-[#9130C0];}.Cyan-Light{@apply bg-[#F1FAFE] border-transparent text-[#096E9B];}.Gray-Outline{@apply bg-[#F4F4F6] border-[#BBBDC5] text-[#363C51];}.Primary-Outline{@apply bg-[#E5F3FF] border-[#3F71FF] text-[#1434CB];}.Error-Outline{@apply bg-[#FFF1F1] border-[#FA727A] text-[#CB1432];}.Warning-Outline{@apply bg-[#FFEED7] border-[#FBAE58] text-[#A04C02];}.Success-Outline{@apply bg-[#F1FCF3] border-[#57D175] text-[#1E7735];}.Purple-Outline{@apply bg-[#F6EAFD] border-[#CE8EF2] text-[#9130C0];}.Cyan-Outline{@apply bg-[#F1FAFE] border-[#3FC2F1] text-[#096E9B];}.Gray-Transparent{@apply bg-transparent border-[#BBBDC5] text-[#363C51];}.Primary-Transparent{@apply bg-transparent border-[#3F71FF] text-[#1434CB];}.Error-Transparent{@apply bg-transparent border-[#FA727A] text-[#CB1432];}.Warning-Transparent{@apply bg-transparent border-[#FBAE58] text-[#A04C02];}.Success-Transparent{@apply bg-transparent border-[#57D175] text-[#1E7735];}.Purple-Transparent{@apply bg-transparent border-[#CE8EF2] text-[#9130C0];}.Cyan-Transparent{@apply bg-transparent border-[#3FC2F1] text-[#096E9B];}\n"] }]
|
|
2563
|
+
}], propDecorators: { label: [{
|
|
2564
|
+
type: Input
|
|
2565
|
+
}], variant: [{
|
|
2566
|
+
type: Input
|
|
2567
|
+
}], color: [{
|
|
2568
|
+
type: Input
|
|
2569
|
+
}], size: [{
|
|
2570
|
+
type: Input
|
|
2571
|
+
}], dot: [{
|
|
2572
|
+
type: Input
|
|
2573
|
+
}], removable: [{
|
|
2574
|
+
type: Input
|
|
2575
|
+
}], customClass: [{
|
|
2576
|
+
type: Input
|
|
2577
|
+
}], clicked: [{
|
|
2578
|
+
type: Output
|
|
2579
|
+
}] } });
|
|
2580
|
+
|
|
2581
|
+
class BkSpinner {
|
|
2582
|
+
size = 'md';
|
|
2583
|
+
show = true;
|
|
2584
|
+
color = 'text-blue-600'; // default
|
|
2585
|
+
get classes() {
|
|
2586
|
+
return `spinner ${this.size} ${this.color}`;
|
|
2587
|
+
}
|
|
2588
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkSpinner, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2589
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkSpinner, isStandalone: true, selector: "bk-spinner", inputs: { size: "size", show: "show", color: "color" }, ngImport: i0, template: "@if (show) {\r\n<span [class]=\"classes\" role=\"status\" aria-label=\"loading\">\r\n <span class=\"sr-only\">Loading...</span>\r\n</span>\r\n}\r\n", styles: [".spinner{@apply inline-block rounded-full animate-spin border-current border-t-transparent;border-style:solid}.spinner.xsm{@apply w-[15px] h-[15px] border-[1.5px];}.spinner.sm{@apply w-[25px] h-[25px] border-[2.34px];}.spinner.md{@apply w-[35px] h-[35px] border-[3.28px];}.spinner.lg{@apply w-[50px] h-[50px] border-[4.7px];}.spinner.xl{@apply w-[64px] h-[64px] border-[6px];}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
2590
|
+
}
|
|
2591
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkSpinner, decorators: [{
|
|
2592
|
+
type: Component,
|
|
2593
|
+
args: [{ selector: 'bk-spinner', standalone: true, imports: [CommonModule], template: "@if (show) {\r\n<span [class]=\"classes\" role=\"status\" aria-label=\"loading\">\r\n <span class=\"sr-only\">Loading...</span>\r\n</span>\r\n}\r\n", styles: [".spinner{@apply inline-block rounded-full animate-spin border-current border-t-transparent;border-style:solid}.spinner.xsm{@apply w-[15px] h-[15px] border-[1.5px];}.spinner.sm{@apply w-[25px] h-[25px] border-[2.34px];}.spinner.md{@apply w-[35px] h-[35px] border-[3.28px];}.spinner.lg{@apply w-[50px] h-[50px] border-[4.7px];}.spinner.xl{@apply w-[64px] h-[64px] border-[6px];}\n"] }]
|
|
2594
|
+
}], propDecorators: { size: [{
|
|
2595
|
+
type: Input
|
|
2596
|
+
}], show: [{
|
|
2597
|
+
type: Input
|
|
2598
|
+
}], color: [{
|
|
2599
|
+
type: Input
|
|
2600
|
+
}] } });
|
|
2601
|
+
|
|
2602
|
+
class BkButton {
|
|
2603
|
+
// --- Inputs ---
|
|
2604
|
+
// 1. Style & Size Inputs
|
|
2605
|
+
variant = 'primary';
|
|
2606
|
+
size = 'md';
|
|
2607
|
+
// 2. Content Inputs
|
|
2608
|
+
label = ''; // Pass text directly
|
|
2609
|
+
leftIcon;
|
|
2610
|
+
rightIcon;
|
|
2611
|
+
iconAlt = 'icon';
|
|
2612
|
+
// 3. State & Config
|
|
2613
|
+
type = 'button';
|
|
2614
|
+
loading = false;
|
|
2615
|
+
disabled = false;
|
|
2616
|
+
// 4. Customization (Optional overrides)
|
|
2617
|
+
buttonClass = ''; // Append extra classes if needed
|
|
2618
|
+
textClass = '';
|
|
2619
|
+
spinnerClass = '';
|
|
2620
|
+
// --- Outputs ---
|
|
2621
|
+
clicked = new EventEmitter();
|
|
2622
|
+
// --- Logic ---
|
|
2623
|
+
onClick(event) {
|
|
2624
|
+
if (!this.disabled && !this.loading) {
|
|
2625
|
+
this.clicked.emit(true);
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
// Generate the class string based on Inputs
|
|
2629
|
+
get buttonClasses() {
|
|
2630
|
+
const variantClass = this.variant === 'primary' ? 'btn-primary' : 'btn-secondary';
|
|
2631
|
+
// Combine: Variant + Size + Custom Classes
|
|
2632
|
+
// Note: The size name (e.g., 'sm') matches your CSS class name exactly
|
|
2633
|
+
return `btn ${variantClass} ${this.size} ${this.buttonClass}`;
|
|
2634
|
+
}
|
|
2635
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2636
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkButton, isStandalone: true, selector: "bk-button", inputs: { variant: "variant", size: "size", label: "label", leftIcon: "leftIcon", rightIcon: "rightIcon", iconAlt: "iconAlt", type: "type", loading: "loading", disabled: "disabled", buttonClass: "buttonClass", textClass: "textClass", spinnerClass: "spinnerClass" }, outputs: { clicked: "clicked" }, ngImport: i0, template: "<button\r\n [attr.type]=\"type\"\r\n [class]=\"buttonClasses\"\r\n [disabled]=\"disabled || loading\"\r\n (click)=\"onClick($event)\"\r\n>\r\n @if (leftIcon) {\r\n <img [src]=\"leftIcon\" [alt]=\"iconAlt\" class=\"icon shrink-0\" />\r\n }\r\n\r\n @if (label) {\r\n <span [class]=\"textClass\">\r\n {{ label }}\r\n </span>\r\n }\r\n @if (loading) {\r\n <span [class]=\"spinnerClass\" class=\"spinner\"></span>\r\n }\r\n\r\n\r\n @if (rightIcon) {\r\n <img [src]=\"rightIcon\" [alt]=\"iconAlt\" class=\"icon shrink-0\" />\r\n }\r\n\r\n</button>\r\n", styles: [".btn{@apply border rounded transition-colors duration-200 cursor-pointer;}.btn-primary{@apply font-medium flex justify-center gap-2 items-center bg-black text-white focus-visible:outline-2 focus-visible:outline-offset-[2.5px] focus-visible:outline-black active:bg-[#242424] disabled:opacity-90 disabled:cursor-not-allowed disabled:bg-[#242424];}.btn-primary.xxl{@apply gap-3;}.btn-secondary{@apply font-medium flex justify-center gap-2 items-center bg-white text-[#6B7080] focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#F5F5FA] active:bg-[#F4F4F6] disabled:opacity-90 disabled:cursor-not-allowed disabled:bg-[#F4F4F6];}.btn.xxsm{@apply px-2 py-1 text-xs leading-[18px];}.btn.xsm{@apply px-3 py-2 text-xs leading-[18px];}.btn.sm{@apply px-[14px] py-2 text-sm;}.btn.md{@apply px-4 py-2.5 text-sm;}.btn.lg{@apply px-[18px] py-2.5 text-base;}.btn.xl{@apply px-5 py-3 text-base;}.btn.xxl{@apply px-7 py-4 text-lg leading-[26px];}.btn.xxsm .icon{@apply size-[11px];}.btn.xsm .icon{@apply size-[15px];}.btn.sm .icon,.btn.md .icon,.btn.lg .icon,.btn.xl .icon{@apply size-4;}.btn.xxl .icon{@apply size-5;}.btn.spinner{@apply shrink-0 border-t-transparent rounded-full animate-spin border-current;}.btn.xxsm .spinner{@apply size-[14px] border-[1.5px];}.btn.xsm .spinner,.btn.sm .spinner,.btn.md .spinner,.btn.lg .spinner,.btn.xl .spinner{@apply size-[15px] border-[1.5px];}.btn.xxl .spinner{@apply size-[24px] border-[2.34px];}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
2637
|
+
}
|
|
2638
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkButton, decorators: [{
|
|
2639
|
+
type: Component,
|
|
2640
|
+
args: [{ selector: 'bk-button', standalone: true, imports: [CommonModule], template: "<button\r\n [attr.type]=\"type\"\r\n [class]=\"buttonClasses\"\r\n [disabled]=\"disabled || loading\"\r\n (click)=\"onClick($event)\"\r\n>\r\n @if (leftIcon) {\r\n <img [src]=\"leftIcon\" [alt]=\"iconAlt\" class=\"icon shrink-0\" />\r\n }\r\n\r\n @if (label) {\r\n <span [class]=\"textClass\">\r\n {{ label }}\r\n </span>\r\n }\r\n @if (loading) {\r\n <span [class]=\"spinnerClass\" class=\"spinner\"></span>\r\n }\r\n\r\n\r\n @if (rightIcon) {\r\n <img [src]=\"rightIcon\" [alt]=\"iconAlt\" class=\"icon shrink-0\" />\r\n }\r\n\r\n</button>\r\n", styles: [".btn{@apply border rounded transition-colors duration-200 cursor-pointer;}.btn-primary{@apply font-medium flex justify-center gap-2 items-center bg-black text-white focus-visible:outline-2 focus-visible:outline-offset-[2.5px] focus-visible:outline-black active:bg-[#242424] disabled:opacity-90 disabled:cursor-not-allowed disabled:bg-[#242424];}.btn-primary.xxl{@apply gap-3;}.btn-secondary{@apply font-medium flex justify-center gap-2 items-center bg-white text-[#6B7080] focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#F5F5FA] active:bg-[#F4F4F6] disabled:opacity-90 disabled:cursor-not-allowed disabled:bg-[#F4F4F6];}.btn.xxsm{@apply px-2 py-1 text-xs leading-[18px];}.btn.xsm{@apply px-3 py-2 text-xs leading-[18px];}.btn.sm{@apply px-[14px] py-2 text-sm;}.btn.md{@apply px-4 py-2.5 text-sm;}.btn.lg{@apply px-[18px] py-2.5 text-base;}.btn.xl{@apply px-5 py-3 text-base;}.btn.xxl{@apply px-7 py-4 text-lg leading-[26px];}.btn.xxsm .icon{@apply size-[11px];}.btn.xsm .icon{@apply size-[15px];}.btn.sm .icon,.btn.md .icon,.btn.lg .icon,.btn.xl .icon{@apply size-4;}.btn.xxl .icon{@apply size-5;}.btn.spinner{@apply shrink-0 border-t-transparent rounded-full animate-spin border-current;}.btn.xxsm .spinner{@apply size-[14px] border-[1.5px];}.btn.xsm .spinner,.btn.sm .spinner,.btn.md .spinner,.btn.lg .spinner,.btn.xl .spinner{@apply size-[15px] border-[1.5px];}.btn.xxl .spinner{@apply size-[24px] border-[2.34px];}\n"] }]
|
|
2641
|
+
}], propDecorators: { variant: [{
|
|
2642
|
+
type: Input
|
|
2643
|
+
}], size: [{
|
|
2644
|
+
type: Input
|
|
2645
|
+
}], label: [{
|
|
2646
|
+
type: Input
|
|
2647
|
+
}], leftIcon: [{
|
|
2648
|
+
type: Input
|
|
2649
|
+
}], rightIcon: [{
|
|
2650
|
+
type: Input
|
|
2651
|
+
}], iconAlt: [{
|
|
2652
|
+
type: Input
|
|
2653
|
+
}], type: [{
|
|
2654
|
+
type: Input
|
|
2655
|
+
}], loading: [{
|
|
2656
|
+
type: Input
|
|
2657
|
+
}], disabled: [{
|
|
2658
|
+
type: Input
|
|
2659
|
+
}], buttonClass: [{
|
|
2660
|
+
type: Input
|
|
2661
|
+
}], textClass: [{
|
|
2662
|
+
type: Input
|
|
2663
|
+
}], spinnerClass: [{
|
|
2664
|
+
type: Input
|
|
2665
|
+
}], clicked: [{
|
|
2666
|
+
type: Output
|
|
2667
|
+
}] } });
|
|
2668
|
+
|
|
2669
|
+
class BkIconButton {
|
|
2670
|
+
// --- Inputs ---
|
|
2671
|
+
icon; // Required icon path
|
|
2672
|
+
alt = 'icon';
|
|
2673
|
+
variant = 'primary';
|
|
2674
|
+
size = 'md';
|
|
2675
|
+
disabled = false;
|
|
2676
|
+
// Custom classes
|
|
2677
|
+
buttonClass = '';
|
|
2678
|
+
clicked = new EventEmitter();
|
|
2679
|
+
onClick(event) {
|
|
2680
|
+
if (!this.disabled) {
|
|
2681
|
+
this.clicked.emit(true);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
get buttonClasses() {
|
|
2685
|
+
// Maps inputs to CSS classes: .btn-icon .primary .md
|
|
2686
|
+
return `btn-icon ${this.variant} ${this.size} ${this.buttonClass}`;
|
|
2687
|
+
}
|
|
2688
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkIconButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2689
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: BkIconButton, isStandalone: true, selector: "bk-icon-button", inputs: { icon: "icon", alt: "alt", variant: "variant", size: "size", disabled: "disabled", buttonClass: "buttonClass" }, outputs: { clicked: "clicked" }, ngImport: i0, template: "<button\r\n [class]=\"buttonClasses\"\r\n [disabled]=\"disabled\"\r\n (click)=\"onClick($event)\"\r\n>\r\n <img [src]=\"icon\" [alt]=\"alt\" class=\"icon\" />\r\n</button>\r\n", styles: [".btn-icon{@apply rounded border flex items-center justify-center transition-all duration-200 cursor-pointer shrink-0 shadow;}.btn-icon.primary{@apply bg-black text-white focus-visible:outline-2 focus-visible:outline-offset-[2.5px] focus-visible:outline-black active:bg-[#242424] disabled:opacity-80 disabled:cursor-not-allowed;}.btn-icon.secondary{@apply bg-white border-[#E3E3E7] text-[#6B7080] focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#F5F5FA] active:bg-[#F4F4F6] disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-[#F4F4F6];}.btn-icon.xxsm,.btn-icon.xsm{@apply p-1.5;}.btn-icon.sm{@apply p-2;}.btn-icon.md{@apply p-2.5;}.btn-icon.lg{@apply p-3;}.btn-icon.xl{@apply p-[14px];}.btn-icon.xxl{@apply p-4;}.btn-icon.xxsm .icon{@apply size-[14px];}.btn-icon.xsm .icon{@apply size-[18px];}.btn-icon.sm .icon,.btn-icon.md .icon,.btn-icon.lg .icon,.btn-icon.xl .icon{@apply size-[20px];}.btn-icon.xxl .icon{@apply size-[26px];}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
2690
|
+
}
|
|
2691
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkIconButton, decorators: [{
|
|
2692
|
+
type: Component,
|
|
2693
|
+
args: [{ selector: 'bk-icon-button', standalone: true, imports: [CommonModule], template: "<button\r\n [class]=\"buttonClasses\"\r\n [disabled]=\"disabled\"\r\n (click)=\"onClick($event)\"\r\n>\r\n <img [src]=\"icon\" [alt]=\"alt\" class=\"icon\" />\r\n</button>\r\n", styles: [".btn-icon{@apply rounded border flex items-center justify-center transition-all duration-200 cursor-pointer shrink-0 shadow;}.btn-icon.primary{@apply bg-black text-white focus-visible:outline-2 focus-visible:outline-offset-[2.5px] focus-visible:outline-black active:bg-[#242424] disabled:opacity-80 disabled:cursor-not-allowed;}.btn-icon.secondary{@apply bg-white border-[#E3E3E7] text-[#6B7080] focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#F5F5FA] active:bg-[#F4F4F6] disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-[#F4F4F6];}.btn-icon.xxsm,.btn-icon.xsm{@apply p-1.5;}.btn-icon.sm{@apply p-2;}.btn-icon.md{@apply p-2.5;}.btn-icon.lg{@apply p-3;}.btn-icon.xl{@apply p-[14px];}.btn-icon.xxl{@apply p-4;}.btn-icon.xxsm .icon{@apply size-[14px];}.btn-icon.xsm .icon{@apply size-[18px];}.btn-icon.sm .icon,.btn-icon.md .icon,.btn-icon.lg .icon,.btn-icon.xl .icon{@apply size-[20px];}.btn-icon.xxl .icon{@apply size-[26px];}\n"] }]
|
|
2694
|
+
}], propDecorators: { icon: [{
|
|
2695
|
+
type: Input
|
|
2696
|
+
}], alt: [{
|
|
2697
|
+
type: Input
|
|
2698
|
+
}], variant: [{
|
|
2699
|
+
type: Input
|
|
2700
|
+
}], size: [{
|
|
2701
|
+
type: Input
|
|
2702
|
+
}], disabled: [{
|
|
2703
|
+
type: Input
|
|
2704
|
+
}], buttonClass: [{
|
|
2705
|
+
type: Input
|
|
2706
|
+
}], clicked: [{
|
|
2707
|
+
type: Output
|
|
2708
|
+
}] } });
|
|
2709
|
+
|
|
2710
|
+
class BkButtonGroup {
|
|
2711
|
+
// --- Inputs ---
|
|
2712
|
+
items = [];
|
|
2713
|
+
mode = 'single';
|
|
2714
|
+
disabled = false;
|
|
2715
|
+
// Holds the current selection.
|
|
2716
|
+
// For 'single', it's a single value. For 'multiple', it's an array.
|
|
2717
|
+
value = null;
|
|
2718
|
+
// --- Outputs ---
|
|
2719
|
+
valueChange = new EventEmitter();
|
|
2720
|
+
// --- Logic ---
|
|
2721
|
+
onItemClick(itemValue) {
|
|
2722
|
+
if (this.disabled)
|
|
2723
|
+
return;
|
|
2724
|
+
if (this.mode === 'single') {
|
|
2725
|
+
// 1. Single Mode: Just set the value
|
|
2726
|
+
if (this.value !== itemValue) {
|
|
2727
|
+
this.value = itemValue;
|
|
2728
|
+
this.valueChange.emit(this.value);
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
else {
|
|
2732
|
+
// 2. Multiple Mode: Toggle value in array
|
|
2733
|
+
let currentValues = Array.isArray(this.value) ? [...this.value] : [];
|
|
2734
|
+
if (currentValues.includes(itemValue)) {
|
|
2735
|
+
// Remove if exists
|
|
2736
|
+
currentValues = currentValues.filter(v => v !== itemValue);
|
|
2737
|
+
}
|
|
2738
|
+
else {
|
|
2739
|
+
// Add if not exists
|
|
2740
|
+
currentValues.push(itemValue);
|
|
2741
|
+
}
|
|
2742
|
+
this.value = currentValues;
|
|
2743
|
+
this.valueChange.emit(this.value);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
// Helper to check active state for UI
|
|
2747
|
+
isActive(itemValue) {
|
|
2748
|
+
if (this.mode === 'single') {
|
|
2749
|
+
return this.value === itemValue;
|
|
2750
|
+
}
|
|
2751
|
+
else {
|
|
2752
|
+
return Array.isArray(this.value) && this.value.includes(itemValue);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
// --- Styles ---
|
|
2756
|
+
get containerClass() {
|
|
2757
|
+
return `group-container ${this.disabled ? 'disabled' : ''}`;
|
|
2758
|
+
}
|
|
2759
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkButtonGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2760
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkButtonGroup, isStandalone: true, selector: "bk-button-group", inputs: { items: "items", mode: "mode", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div [class]=\"containerClass\">\r\n @for (item of items; track item.value) {\r\n <div\r\n class=\"group-item\"\r\n [class.active]=\"isActive(item.value)\"\r\n [class.inactive]=\"!isActive(item.value)\"\r\n (click)=\"onItemClick(item.value)\"\r\n >\r\n {{ item.label }}\r\n </div>\r\n }\r\n</div>\r\n", styles: [".group-container{@apply inline-flex items-center border border-[#E1E3EA] bg-white rounded-md p-1 gap-2.5 select-none transition-all;}.group-item{@apply flex items-center justify-center font-medium rounded-md transition-all duration-200 cursor-pointer text-center relative;}.group-item.active{@apply bg-black text-white shadow-sm;}.group-item.inactive{@apply text-[#6B7080] hover:bg-gray-50;}.group-container .group-item{@apply px-4 py-[3px] text-[13px] leading-5;}.group-container.disabled .group-item{@apply opacity-50 cursor-not-allowed;}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
2761
|
+
}
|
|
2762
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkButtonGroup, decorators: [{
|
|
2763
|
+
type: Component,
|
|
2764
|
+
args: [{ selector: 'bk-button-group', standalone: true, imports: [CommonModule], template: "<div [class]=\"containerClass\">\r\n @for (item of items; track item.value) {\r\n <div\r\n class=\"group-item\"\r\n [class.active]=\"isActive(item.value)\"\r\n [class.inactive]=\"!isActive(item.value)\"\r\n (click)=\"onItemClick(item.value)\"\r\n >\r\n {{ item.label }}\r\n </div>\r\n }\r\n</div>\r\n", styles: [".group-container{@apply inline-flex items-center border border-[#E1E3EA] bg-white rounded-md p-1 gap-2.5 select-none transition-all;}.group-item{@apply flex items-center justify-center font-medium rounded-md transition-all duration-200 cursor-pointer text-center relative;}.group-item.active{@apply bg-black text-white shadow-sm;}.group-item.inactive{@apply text-[#6B7080] hover:bg-gray-50;}.group-container .group-item{@apply px-4 py-[3px] text-[13px] leading-5;}.group-container.disabled .group-item{@apply opacity-50 cursor-not-allowed;}\n"] }]
|
|
2765
|
+
}], propDecorators: { items: [{
|
|
2766
|
+
type: Input
|
|
2767
|
+
}], mode: [{
|
|
2768
|
+
type: Input
|
|
2769
|
+
}], disabled: [{
|
|
2770
|
+
type: Input
|
|
2771
|
+
}], value: [{
|
|
2772
|
+
type: Input
|
|
2773
|
+
}], valueChange: [{
|
|
2774
|
+
type: Output
|
|
2775
|
+
}] } });
|
|
2776
|
+
|
|
2777
|
+
class BkTextarea {
|
|
2778
|
+
ngControl;
|
|
2779
|
+
autoComplete = 'off';
|
|
2780
|
+
name;
|
|
2781
|
+
id;
|
|
2782
|
+
label = '';
|
|
2783
|
+
placeholder = '';
|
|
2784
|
+
rows = 4;
|
|
2785
|
+
hint = '';
|
|
2786
|
+
required = false;
|
|
2787
|
+
maxlength = null;
|
|
2788
|
+
minlength = null;
|
|
2789
|
+
hasError = false;
|
|
2790
|
+
disabled = false;
|
|
2791
|
+
errorMessage = '';
|
|
2792
|
+
tabIndex = null;
|
|
2793
|
+
readOnly = false;
|
|
2794
|
+
autoCapitalize = null;
|
|
2795
|
+
inputMode = null;
|
|
2796
|
+
input = new EventEmitter();
|
|
2797
|
+
change = new EventEmitter();
|
|
2798
|
+
blur = new EventEmitter();
|
|
2799
|
+
focus = new EventEmitter();
|
|
2800
|
+
value = '';
|
|
2801
|
+
// --- ControlValueAccessor ---
|
|
2802
|
+
onChange = (_) => { };
|
|
2803
|
+
onTouched = () => { };
|
|
2804
|
+
constructor(ngControl) {
|
|
2805
|
+
this.ngControl = ngControl;
|
|
2806
|
+
if (this.ngControl) {
|
|
2807
|
+
this.ngControl.valueAccessor = this;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
// --- Expose FormControl state ---
|
|
2811
|
+
get control() {
|
|
2812
|
+
return this.ngControl?.control;
|
|
2813
|
+
}
|
|
2814
|
+
get touched() {
|
|
2815
|
+
return this.control?.touched;
|
|
2816
|
+
}
|
|
2817
|
+
get dirty() {
|
|
2818
|
+
return this.control?.dirty;
|
|
2819
|
+
}
|
|
2820
|
+
get errors() {
|
|
2821
|
+
return this.control?.errors;
|
|
2822
|
+
}
|
|
2823
|
+
handleFocus(event) {
|
|
2824
|
+
this.focus.emit(event);
|
|
2825
|
+
}
|
|
2826
|
+
handleBlur(event) {
|
|
2827
|
+
this.onTouched();
|
|
2828
|
+
this.blur.emit(event);
|
|
2829
|
+
}
|
|
2830
|
+
handleInput(event) {
|
|
2831
|
+
const val = event.target.value;
|
|
2832
|
+
this.value = val; // update CVA value
|
|
2833
|
+
this.onChange(val); // propagate to parent form
|
|
2834
|
+
this.input.emit(event); // emit raw event
|
|
2835
|
+
}
|
|
2836
|
+
handleChange(event) {
|
|
2837
|
+
this.change.emit(event); // emit raw change event
|
|
2838
|
+
}
|
|
2839
|
+
writeValue(value) {
|
|
2840
|
+
this.value = value ?? '';
|
|
2841
|
+
}
|
|
2842
|
+
registerOnChange(fn) {
|
|
2843
|
+
this.onChange = fn;
|
|
2844
|
+
}
|
|
2845
|
+
registerOnTouched(fn) {
|
|
2846
|
+
this.onTouched = fn;
|
|
2847
|
+
}
|
|
2848
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkTextarea, deps: [{ token: i1$1.NgControl, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
2849
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkTextarea, isStandalone: true, selector: "bk-textarea", inputs: { autoComplete: "autoComplete", name: "name", id: "id", label: "label", placeholder: "placeholder", rows: "rows", hint: "hint", required: "required", maxlength: "maxlength", minlength: "minlength", hasError: "hasError", disabled: "disabled", errorMessage: "errorMessage", tabIndex: "tabIndex", readOnly: "readOnly", autoCapitalize: "autoCapitalize", inputMode: "inputMode" }, outputs: { input: "input", change: "change", blur: "blur", focus: "focus" }, ngImport: i0, template: "<div class=\"flex flex-col gap-1.5 w-full\">\r\n\r\n @if (label) {\r\n <label\r\n class=\"text-sm font-medium text-[#141414] block\" [for]=\"id\">\r\n {{ label }}\r\n @if (required) {\r\n <span class=\"text-[#E7000B] ml-0.5\">*</span>\r\n }\r\n </label>\r\n }\r\n\r\n <div class=\"relative\">\r\n <textarea\r\n [id]=\"id\"\r\n [name]=\"name\"\r\n [disabled]=\"disabled\"\r\n [tabindex]=\"tabIndex\"\r\n [readOnly]=\"readOnly\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [autocomplete]=\"autoComplete\"\r\n [autocapitalize]=\"autoCapitalize\"\r\n [inputMode]=\"inputMode\"\r\n [value]=\"value\"\r\n (input)=\"handleInput($event)\"\r\n (change)=\"handleChange($event)\"\r\n (blur)=\"handleBlur($event)\"\r\n (focus)=\"handleFocus($event)\"\r\n [placeholder]=\"placeholder\"\r\n\r\n [autocomplete]=\"autoComplete\"\r\n\r\n rows=\"{{rows}}\"\r\n class=\"\r\n w-full\r\n px-3 py-2.5\r\n text-sm\r\n border border-[#E3E3E7] rounded-[4px]\r\n outline-none\r\n transition-colors duration-200\r\n bg-white resize-y\r\n placeholder:text-[#6B7080]\r\n \"\r\n [ngClass]=\"{\r\n 'border-[#FA727A] text-[#141414]': hasError && !disabled,\r\n\r\n 'focus:border-[#6B7080] text-[#141414]': !hasError && !disabled,\r\n\r\n 'bg-[#F4F4F6] text-[#A1A3AE] border-[#E3E3E7] cursor-not-allowed': disabled\r\n }\"\r\n ></textarea>\r\n </div>\r\n\r\n <div class=\"flex justify-between items-start font-normal text-sm\">\r\n\r\n <div class=\"flex-1\">\r\n @if (hasError) {\r\n <span class=\"text-[#F34050]\">\r\n {{ errorMessage }}\r\n </span>\r\n } @else if (hint) {\r\n <span class=\"text-[#868997]\">\r\n {{ hint }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (maxlength) {\r\n <div\r\n class=\"text-[#868997] tabular-nums flex-shrink-0\"\r\n >\r\n {{ value.length }}/{{ maxlength }}\r\n </div>\r\n }\r\n\r\n\r\n </div>\r\n\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }] });
|
|
2850
|
+
}
|
|
2851
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkTextarea, decorators: [{
|
|
2852
|
+
type: Component,
|
|
2853
|
+
args: [{ selector: 'bk-textarea', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"flex flex-col gap-1.5 w-full\">\r\n\r\n @if (label) {\r\n <label\r\n class=\"text-sm font-medium text-[#141414] block\" [for]=\"id\">\r\n {{ label }}\r\n @if (required) {\r\n <span class=\"text-[#E7000B] ml-0.5\">*</span>\r\n }\r\n </label>\r\n }\r\n\r\n <div class=\"relative\">\r\n <textarea\r\n [id]=\"id\"\r\n [name]=\"name\"\r\n [disabled]=\"disabled\"\r\n [tabindex]=\"tabIndex\"\r\n [readOnly]=\"readOnly\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [autocomplete]=\"autoComplete\"\r\n [autocapitalize]=\"autoCapitalize\"\r\n [inputMode]=\"inputMode\"\r\n [value]=\"value\"\r\n (input)=\"handleInput($event)\"\r\n (change)=\"handleChange($event)\"\r\n (blur)=\"handleBlur($event)\"\r\n (focus)=\"handleFocus($event)\"\r\n [placeholder]=\"placeholder\"\r\n\r\n [autocomplete]=\"autoComplete\"\r\n\r\n rows=\"{{rows}}\"\r\n class=\"\r\n w-full\r\n px-3 py-2.5\r\n text-sm\r\n border border-[#E3E3E7] rounded-[4px]\r\n outline-none\r\n transition-colors duration-200\r\n bg-white resize-y\r\n placeholder:text-[#6B7080]\r\n \"\r\n [ngClass]=\"{\r\n 'border-[#FA727A] text-[#141414]': hasError && !disabled,\r\n\r\n 'focus:border-[#6B7080] text-[#141414]': !hasError && !disabled,\r\n\r\n 'bg-[#F4F4F6] text-[#A1A3AE] border-[#E3E3E7] cursor-not-allowed': disabled\r\n }\"\r\n ></textarea>\r\n </div>\r\n\r\n <div class=\"flex justify-between items-start font-normal text-sm\">\r\n\r\n <div class=\"flex-1\">\r\n @if (hasError) {\r\n <span class=\"text-[#F34050]\">\r\n {{ errorMessage }}\r\n </span>\r\n } @else if (hint) {\r\n <span class=\"text-[#868997]\">\r\n {{ hint }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (maxlength) {\r\n <div\r\n class=\"text-[#868997] tabular-nums flex-shrink-0\"\r\n >\r\n {{ value.length }}/{{ maxlength }}\r\n </div>\r\n }\r\n\r\n\r\n </div>\r\n\r\n</div>\r\n" }]
|
|
2854
|
+
}], ctorParameters: () => [{ type: i1$1.NgControl, decorators: [{
|
|
2855
|
+
type: Optional
|
|
2856
|
+
}, {
|
|
2857
|
+
type: Self
|
|
2858
|
+
}] }], propDecorators: { autoComplete: [{
|
|
2859
|
+
type: Input
|
|
2860
|
+
}], name: [{
|
|
2861
|
+
type: Input
|
|
2862
|
+
}], id: [{
|
|
2863
|
+
type: Input
|
|
2864
|
+
}], label: [{
|
|
2865
|
+
type: Input
|
|
2866
|
+
}], placeholder: [{
|
|
2867
|
+
type: Input
|
|
2868
|
+
}], rows: [{
|
|
2869
|
+
type: Input
|
|
2870
|
+
}], hint: [{
|
|
2871
|
+
type: Input
|
|
2872
|
+
}], required: [{
|
|
2873
|
+
type: Input
|
|
2874
|
+
}], maxlength: [{
|
|
2875
|
+
type: Input
|
|
2876
|
+
}], minlength: [{
|
|
2877
|
+
type: Input
|
|
2878
|
+
}], hasError: [{
|
|
2879
|
+
type: Input
|
|
2880
|
+
}], disabled: [{
|
|
2881
|
+
type: Input
|
|
2882
|
+
}], errorMessage: [{
|
|
2883
|
+
type: Input
|
|
2884
|
+
}], tabIndex: [{
|
|
2885
|
+
type: Input
|
|
2886
|
+
}], readOnly: [{
|
|
2887
|
+
type: Input
|
|
2888
|
+
}], autoCapitalize: [{
|
|
2889
|
+
type: Input
|
|
2890
|
+
}], inputMode: [{
|
|
2891
|
+
type: Input
|
|
2892
|
+
}], input: [{
|
|
2893
|
+
type: Output
|
|
2894
|
+
}], change: [{
|
|
2895
|
+
type: Output
|
|
2896
|
+
}], blur: [{
|
|
2897
|
+
type: Output
|
|
2898
|
+
}], focus: [{
|
|
2899
|
+
type: Output
|
|
2900
|
+
}] } });
|
|
2901
|
+
|
|
2902
|
+
class BkGrid {
|
|
2903
|
+
draggable = false;
|
|
2904
|
+
columns = [];
|
|
2905
|
+
result;
|
|
2906
|
+
actions = [];
|
|
2907
|
+
actionClick = new EventEmitter();
|
|
2908
|
+
sortChange = new EventEmitter();
|
|
2909
|
+
dragDropChange = new EventEmitter();
|
|
2910
|
+
sortColumn;
|
|
2911
|
+
sortDirection = 'asc';
|
|
2912
|
+
tableScrollContainer;
|
|
2913
|
+
get firstVisibleColumnIndex() {
|
|
2914
|
+
const index = this.columns.findIndex(col => col.visible !== false);
|
|
2915
|
+
return index >= 0 ? index : 0;
|
|
2916
|
+
}
|
|
2917
|
+
/* ---------- Sorting ---------- */
|
|
2918
|
+
sort(column, index) {
|
|
2919
|
+
if (!column.sortable || !column.field)
|
|
2920
|
+
return;
|
|
2921
|
+
// Toggle sort direction
|
|
2922
|
+
this.sortDirection =
|
|
2923
|
+
this.sortColumn === column.field ? (this.sortDirection === 'asc' ? 'desc' : 'asc') : 'asc';
|
|
2924
|
+
this.sortColumn = column.field;
|
|
2925
|
+
// Emit sort change separately
|
|
2926
|
+
this.sortChange.emit({
|
|
2927
|
+
columnIndex: index,
|
|
2928
|
+
column,
|
|
2929
|
+
direction: this.sortDirection,
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
/* ---------- Visibility ---------- */
|
|
2933
|
+
isColumnVisible(column) {
|
|
2934
|
+
return column.visible !== false;
|
|
2935
|
+
}
|
|
2936
|
+
/* ---------- Cell Value ---------- */
|
|
2937
|
+
getCellValue(row, column) {
|
|
2938
|
+
if (column.formatter) {
|
|
2939
|
+
return column.formatter(row);
|
|
2940
|
+
}
|
|
2941
|
+
if (column.field) {
|
|
2942
|
+
return String(row[column.field] ?? '');
|
|
2943
|
+
}
|
|
2944
|
+
return '';
|
|
2945
|
+
}
|
|
2946
|
+
/* ---------- Actions ---------- */
|
|
2947
|
+
emitAction(action, row) {
|
|
2948
|
+
this.actionClick.emit({
|
|
2949
|
+
action: action.name,
|
|
2950
|
+
row,
|
|
2951
|
+
});
|
|
2952
|
+
}
|
|
2953
|
+
dropList(event) {
|
|
2954
|
+
if (!this.draggable || !this.result)
|
|
2955
|
+
return;
|
|
2956
|
+
moveItemInArray(this.result, event.previousIndex, event.currentIndex);
|
|
2957
|
+
// Update existing sortOrder on T
|
|
2958
|
+
this.result.forEach((item, index) => {
|
|
2959
|
+
item.sortOrder = index + 1;
|
|
2960
|
+
});
|
|
2961
|
+
// Emit reordered list
|
|
2962
|
+
this.dragDropChange.emit(this.result);
|
|
2963
|
+
}
|
|
2964
|
+
onDragMoved(event) {
|
|
2965
|
+
if (!this.tableScrollContainer)
|
|
2966
|
+
return;
|
|
2967
|
+
const container = this.tableScrollContainer.nativeElement;
|
|
2968
|
+
const rect = container.getBoundingClientRect();
|
|
2969
|
+
const pointerY = event.pointerPosition.y - rect.top;
|
|
2970
|
+
const threshold = 80;
|
|
2971
|
+
const maxSpeed = 25;
|
|
2972
|
+
if (pointerY < threshold) {
|
|
2973
|
+
const intensity = 1 - pointerY / threshold;
|
|
2974
|
+
container.scrollTop -= Math.min(maxSpeed, intensity * maxSpeed);
|
|
2975
|
+
}
|
|
2976
|
+
else if (pointerY > rect.height - threshold) {
|
|
2977
|
+
const intensity = 1 - (rect.height - pointerY) / threshold;
|
|
2978
|
+
container.scrollTop += Math.min(maxSpeed, intensity * maxSpeed);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
onDragStart(event) {
|
|
2982
|
+
const row = event.source.element.nativeElement;
|
|
2983
|
+
const cells = Array.from(row.querySelectorAll('td'));
|
|
2984
|
+
setTimeout(() => {
|
|
2985
|
+
const preview = document.querySelector('.cdk-drag-preview');
|
|
2986
|
+
if (!preview)
|
|
2987
|
+
return;
|
|
2988
|
+
const previewCells = preview.querySelectorAll('td');
|
|
2989
|
+
cells.forEach((cell, index) => {
|
|
2990
|
+
const width = cell.getBoundingClientRect().width + 'px';
|
|
2991
|
+
const previewCell = previewCells[index];
|
|
2992
|
+
if (previewCell) {
|
|
2993
|
+
previewCell.style.width = width;
|
|
2994
|
+
previewCell.style.minWidth = width;
|
|
2995
|
+
previewCell.style.maxWidth = width;
|
|
2996
|
+
}
|
|
2997
|
+
});
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3001
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkGrid, isStandalone: true, selector: "bk-grid", inputs: { draggable: "draggable", columns: "columns", result: "result", actions: "actions" }, outputs: { actionClick: "actionClick", sortChange: "sortChange", dragDropChange: "dragDropChange" }, viewQueries: [{ propertyName: "tableScrollContainer", first: true, predicate: ["tableScrollContainer"], descendants: true }], ngImport: i0, template: "<div\r\n #tableScrollContainer\r\n cdkScrollable\r\n class=\"h-[calc(100vh-260px)] overflow-y-auto\">\r\n\r\n <table class=\"min-w-full text-sm text-left text-gray-800 table-auto border-collapse\">\r\n\r\n <!-- ================= HEADER ================= -->\r\n <thead>\r\n <tr>\r\n @for (col of columns; track col.header;let i=$index;) {\r\n @if (isColumnVisible(col)) {\r\n <th\r\n class=\"grid-header sticky top-0 cursor-pointer\"\r\n [class.action-sticky]=\"col.sticky\"\r\n [class.z-10]=\"col.sticky\"\r\n [ngClass]=\"col.headerClass\"\r\n [ngClass]=\"col.cellClass\"\r\n (click)=\"sort(col,i)\"\r\n >\r\n <span class=\"flex items-center gap-1\"\r\n [ngClass]=\"sortColumn === col.field\r\n ? (sortDirection === 'asc' ? 'grid-asc' : 'grid-desc')\r\n : ''\">\r\n {{ col.header }}\r\n @if (col.sortable) {\r\n <span class=\"grid-sort-icon\"></span>\r\n }\r\n </span>\r\n </th>\r\n }\r\n }\r\n\r\n @if (actions.length) {\r\n <th class=\"grid-header sticky top-0 action-sticky z-10 !bg-[#FBFBFC] w-20\">\r\n Action\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <!-- ================= BODY ================= -->\r\n <tbody\r\n cdkDropList\r\n [cdkDropListDisabled]=\"!draggable\"\r\n [cdkDropListData]=\"result || []\"\r\n (cdkDropListDropped)=\"dropList($event)\">\r\n\r\n @for (row of result; track row; let rowIndex = $index) {\r\n <tr\r\n cdkDrag\r\n cdkDragLockAxis=\"y\"\r\n [cdkDragDisabled]=\"!draggable\"\r\n (cdkDragStarted)=\"onDragStart($event)\"\r\n (cdkDragMoved)=\"onDragMoved($event)\"\r\n class=\"\" [ngClass]=\"{ 'cursor-move ': draggable }\">\r\n\r\n @for (col of columns; track col.header; let colIndex = $index) {\r\n @if (isColumnVisible(col)) {\r\n <td class=\"grid-cell text-nowrap\" [ngClass]=\"col.cellClass\">\r\n @if (draggable && colIndex === firstVisibleColumnIndex) {\r\n <span cdkDragHandle class=\"mr-2 text-gray-400\" [ngClass]=\"{ 'cursor-move': draggable }\">\u2630</span>\r\n }\r\n {{ getCellValue(row, col) }}\r\n </td>\r\n }\r\n }\r\n\r\n @if (actions.length) {\r\n <td class=\"grid-cell action-sticky text-center\">\r\n <div class=\"flex items-center justify-center gap-1.5\">\r\n @for (action of actions; track action.name) {\r\n @if (action.hasPermission) {\r\n <!-- appTooltip=\"{{ action.tooltip }}\"\r\n appTooltipPosition=\"top\" -->\r\n <button\r\n class=\"size-6 flex items-center justify-center rounded hover:bg-[#F8F8FA]\"\r\n (click)=\"emitAction(action, row)\"\r\n\r\n >\r\n <img\r\n [src]=\"action.icon\"\r\n width=\"14\"\r\n height=\"14\"\r\n alt=\"action-icon\"\r\n />\r\n </button>\r\n }\r\n }\r\n </div>\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n</div>\r\n", styles: [".grid-header{@apply bg-[#FBFBFC] text-xs text-[#60646C] leading-5 font-semibold capitalize px-4 py-2 whitespace-nowrap;}.grid-cell{@apply text-[#15191E] text-[13px] font-medium leading-4 px-4 py-2 border border-[#EBEDF3] border-b-0;}.grid-cell:last-child{@apply border-e-0;}.grid-cell:first-child,.grid-first-cell{@apply border-s-0;}.grid-last-cell{@apply border-e-0;}.grid-action-sticky{@apply sticky bg-white right-[.1px] z-[1px];}.grid-action-sticky:before{@apply absolute top-0 bottom-0 left-[-8px] w-2;content:\"\";background-image:linear-gradient(to left,rgba(0,0,0,.05),transparent)}.grid-required{@apply font-medium text-sm leading-normal after:content-[\"*\"] after:text-[#C10007] after:ms-0.5;}.grid-sort{display:inline-flex;align-items:center;gap:.35rem;cursor:pointer;line-height:1}.grid-sort-icon{display:inline-flex;flex-direction:column;justify-content:center;align-items:center;height:.875rem;width:.875rem;gap:.125rem;line-height:1}.grid-sort-icon:before{display:inline-block;content:\"\";height:.3rem;width:.54rem;background-repeat:no-repeat;background-position:center;background-size:cover;background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M1.08333 4.83333C0.908333 4.83333 0.791667 4.775 0.675 4.65833C0.441667 4.425 0.441667 4.075 0.675 3.84167L3.59167 0.925C3.825 0.691667 4.175 0.691667 4.40833 0.925L7.325 3.84167C7.55833 4.075 7.55833 4.425 7.325 4.65833C7.09167 4.89167 6.74167 4.89167 6.50833 4.65833L4 2.15L1.49167 4.65833C1.375 4.775 1.25833 4.83333 1.08333 4.83333Z' fill='%2378829D'/%3e%3c/svg%3e\")}.grid-sort-icon:after{display:inline-block;content:\"\";height:.3rem;width:.54rem;background-repeat:no-repeat;background-position:center;background-size:cover;background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M4 4.24984C3.825 4.24984 3.70833 4.1915 3.59167 4.07484L0.675 1.15817C0.441667 0.924838 0.441667 0.574837 0.675 0.341504C0.908333 0.108171 1.25833 0.108171 1.49167 0.341504L4 2.84984L6.50833 0.341504C6.74167 0.108171 7.09167 0.108171 7.325 0.341504C7.55833 0.574837 7.55833 0.924838 7.325 1.15817L4.40833 4.07484C4.29167 4.1915 4.175 4.24984 4 4.24984Z' fill='%2378829D'/%3e%3c/svg%3e\")}.grid-asc>.grid-sort-icon:before{background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M1.08333 4.83333C0.908333 4.83333 0.791667 4.775 0.675 4.65833C0.441667 4.425 0.441667 4.075 0.675 3.84167L3.59167 0.925C3.825 0.691667 4.175 0.691667 4.40833 0.925L7.325 3.84167C7.55833 4.075 7.55833 4.425 7.325 4.65833C7.09167 4.89167 6.74167 4.89167 6.50833 4.65833L4 2.15L1.49167 4.65833C1.375 4.775 1.25833 4.83333 1.08333 4.83333Z' fill='%234B5675'/%3e%3c/svg%3e\")}.grid-asc>.grid-sort-icon:after{background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M4 4.24984C3.825 4.24984 3.70833 4.1915 3.59167 4.07484L0.675 1.15817C0.441667 0.924838 0.441667 0.574837 0.675 0.341504C0.908333 0.108171 1.25833 0.108171 1.49167 0.341504L4 2.84984L6.50833 0.341504C6.74167 0.108171 7.09167 0.108171 7.325 0.341504C7.55833 0.574837 7.55833 0.924838 7.325 1.15817L4.40833 4.07484C4.29167 4.1915 4.175 4.24984 4 4.24984Z' fill='%23C4CADA'/%3e%3c/svg%3e\")}.grid-desc>.grid-sort-icon:before{background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M1.08333 4.83333C0.908333 4.83333 0.791667 4.775 0.675 4.65833C0.441667 4.425 0.441667 4.075 0.675 3.84167L3.59167 0.925C3.825 0.691667 4.175 0.691667 4.40833 0.925L7.325 3.84167C7.55833 4.075 7.55833 4.425 7.325 4.65833C7.09167 4.89167 6.74167 4.89167 6.50833 4.65833L4 2.15L1.49167 4.65833C1.375 4.775 1.25833 4.83333 1.08333 4.83333Z' fill='%23C4CADA'/%3e%3c/svg%3e\")}.grid-desc>.grid-sort-icon:after{background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M4 4.24984C3.825 4.24984 3.70833 4.1915 3.59167 4.07484L0.675 1.15817C0.441667 0.924838 0.441667 0.574837 0.675 0.341504C0.908333 0.108171 1.25833 0.108171 1.49167 0.341504L4 2.84984L6.50833 0.341504C6.74167 0.108171 7.09167 0.108171 7.325 0.341504C7.55833 0.574837 7.55833 0.924838 7.325 1.15817L4.40833 4.07484C4.29167 4.1915 4.175 4.24984 4 4.24984Z' fill='%234B5675'/%3e%3c/svg%3e\")}.cdk-drag-preview{display:table;width:100%;background:#fff;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f}.cdk-drag-placeholder{opacity:.4;background-color:#f3f4f6}.cdk-drag-animating,.cdk-drop-list-dragging .cdk-drag{transition:transform .25s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i2.ɵɵCdkScrollable, selector: "[cdk-scrollable], [cdkScrollable]" }, { kind: "directive", type: i2.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i2.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i2.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: ScrollingModule }] });
|
|
3002
|
+
}
|
|
3003
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkGrid, decorators: [{
|
|
3004
|
+
type: Component,
|
|
3005
|
+
args: [{ selector: 'bk-grid', standalone: true, imports: [CommonModule, DragDropModule, ScrollingModule], template: "<div\r\n #tableScrollContainer\r\n cdkScrollable\r\n class=\"h-[calc(100vh-260px)] overflow-y-auto\">\r\n\r\n <table class=\"min-w-full text-sm text-left text-gray-800 table-auto border-collapse\">\r\n\r\n <!-- ================= HEADER ================= -->\r\n <thead>\r\n <tr>\r\n @for (col of columns; track col.header;let i=$index;) {\r\n @if (isColumnVisible(col)) {\r\n <th\r\n class=\"grid-header sticky top-0 cursor-pointer\"\r\n [class.action-sticky]=\"col.sticky\"\r\n [class.z-10]=\"col.sticky\"\r\n [ngClass]=\"col.headerClass\"\r\n [ngClass]=\"col.cellClass\"\r\n (click)=\"sort(col,i)\"\r\n >\r\n <span class=\"flex items-center gap-1\"\r\n [ngClass]=\"sortColumn === col.field\r\n ? (sortDirection === 'asc' ? 'grid-asc' : 'grid-desc')\r\n : ''\">\r\n {{ col.header }}\r\n @if (col.sortable) {\r\n <span class=\"grid-sort-icon\"></span>\r\n }\r\n </span>\r\n </th>\r\n }\r\n }\r\n\r\n @if (actions.length) {\r\n <th class=\"grid-header sticky top-0 action-sticky z-10 !bg-[#FBFBFC] w-20\">\r\n Action\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <!-- ================= BODY ================= -->\r\n <tbody\r\n cdkDropList\r\n [cdkDropListDisabled]=\"!draggable\"\r\n [cdkDropListData]=\"result || []\"\r\n (cdkDropListDropped)=\"dropList($event)\">\r\n\r\n @for (row of result; track row; let rowIndex = $index) {\r\n <tr\r\n cdkDrag\r\n cdkDragLockAxis=\"y\"\r\n [cdkDragDisabled]=\"!draggable\"\r\n (cdkDragStarted)=\"onDragStart($event)\"\r\n (cdkDragMoved)=\"onDragMoved($event)\"\r\n class=\"\" [ngClass]=\"{ 'cursor-move ': draggable }\">\r\n\r\n @for (col of columns; track col.header; let colIndex = $index) {\r\n @if (isColumnVisible(col)) {\r\n <td class=\"grid-cell text-nowrap\" [ngClass]=\"col.cellClass\">\r\n @if (draggable && colIndex === firstVisibleColumnIndex) {\r\n <span cdkDragHandle class=\"mr-2 text-gray-400\" [ngClass]=\"{ 'cursor-move': draggable }\">\u2630</span>\r\n }\r\n {{ getCellValue(row, col) }}\r\n </td>\r\n }\r\n }\r\n\r\n @if (actions.length) {\r\n <td class=\"grid-cell action-sticky text-center\">\r\n <div class=\"flex items-center justify-center gap-1.5\">\r\n @for (action of actions; track action.name) {\r\n @if (action.hasPermission) {\r\n <!-- appTooltip=\"{{ action.tooltip }}\"\r\n appTooltipPosition=\"top\" -->\r\n <button\r\n class=\"size-6 flex items-center justify-center rounded hover:bg-[#F8F8FA]\"\r\n (click)=\"emitAction(action, row)\"\r\n\r\n >\r\n <img\r\n [src]=\"action.icon\"\r\n width=\"14\"\r\n height=\"14\"\r\n alt=\"action-icon\"\r\n />\r\n </button>\r\n }\r\n }\r\n </div>\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n</div>\r\n", styles: [".grid-header{@apply bg-[#FBFBFC] text-xs text-[#60646C] leading-5 font-semibold capitalize px-4 py-2 whitespace-nowrap;}.grid-cell{@apply text-[#15191E] text-[13px] font-medium leading-4 px-4 py-2 border border-[#EBEDF3] border-b-0;}.grid-cell:last-child{@apply border-e-0;}.grid-cell:first-child,.grid-first-cell{@apply border-s-0;}.grid-last-cell{@apply border-e-0;}.grid-action-sticky{@apply sticky bg-white right-[.1px] z-[1px];}.grid-action-sticky:before{@apply absolute top-0 bottom-0 left-[-8px] w-2;content:\"\";background-image:linear-gradient(to left,rgba(0,0,0,.05),transparent)}.grid-required{@apply font-medium text-sm leading-normal after:content-[\"*\"] after:text-[#C10007] after:ms-0.5;}.grid-sort{display:inline-flex;align-items:center;gap:.35rem;cursor:pointer;line-height:1}.grid-sort-icon{display:inline-flex;flex-direction:column;justify-content:center;align-items:center;height:.875rem;width:.875rem;gap:.125rem;line-height:1}.grid-sort-icon:before{display:inline-block;content:\"\";height:.3rem;width:.54rem;background-repeat:no-repeat;background-position:center;background-size:cover;background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M1.08333 4.83333C0.908333 4.83333 0.791667 4.775 0.675 4.65833C0.441667 4.425 0.441667 4.075 0.675 3.84167L3.59167 0.925C3.825 0.691667 4.175 0.691667 4.40833 0.925L7.325 3.84167C7.55833 4.075 7.55833 4.425 7.325 4.65833C7.09167 4.89167 6.74167 4.89167 6.50833 4.65833L4 2.15L1.49167 4.65833C1.375 4.775 1.25833 4.83333 1.08333 4.83333Z' fill='%2378829D'/%3e%3c/svg%3e\")}.grid-sort-icon:after{display:inline-block;content:\"\";height:.3rem;width:.54rem;background-repeat:no-repeat;background-position:center;background-size:cover;background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M4 4.24984C3.825 4.24984 3.70833 4.1915 3.59167 4.07484L0.675 1.15817C0.441667 0.924838 0.441667 0.574837 0.675 0.341504C0.908333 0.108171 1.25833 0.108171 1.49167 0.341504L4 2.84984L6.50833 0.341504C6.74167 0.108171 7.09167 0.108171 7.325 0.341504C7.55833 0.574837 7.55833 0.924838 7.325 1.15817L4.40833 4.07484C4.29167 4.1915 4.175 4.24984 4 4.24984Z' fill='%2378829D'/%3e%3c/svg%3e\")}.grid-asc>.grid-sort-icon:before{background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M1.08333 4.83333C0.908333 4.83333 0.791667 4.775 0.675 4.65833C0.441667 4.425 0.441667 4.075 0.675 3.84167L3.59167 0.925C3.825 0.691667 4.175 0.691667 4.40833 0.925L7.325 3.84167C7.55833 4.075 7.55833 4.425 7.325 4.65833C7.09167 4.89167 6.74167 4.89167 6.50833 4.65833L4 2.15L1.49167 4.65833C1.375 4.775 1.25833 4.83333 1.08333 4.83333Z' fill='%234B5675'/%3e%3c/svg%3e\")}.grid-asc>.grid-sort-icon:after{background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M4 4.24984C3.825 4.24984 3.70833 4.1915 3.59167 4.07484L0.675 1.15817C0.441667 0.924838 0.441667 0.574837 0.675 0.341504C0.908333 0.108171 1.25833 0.108171 1.49167 0.341504L4 2.84984L6.50833 0.341504C6.74167 0.108171 7.09167 0.108171 7.325 0.341504C7.55833 0.574837 7.55833 0.924838 7.325 1.15817L4.40833 4.07484C4.29167 4.1915 4.175 4.24984 4 4.24984Z' fill='%23C4CADA'/%3e%3c/svg%3e\")}.grid-desc>.grid-sort-icon:before{background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M1.08333 4.83333C0.908333 4.83333 0.791667 4.775 0.675 4.65833C0.441667 4.425 0.441667 4.075 0.675 3.84167L3.59167 0.925C3.825 0.691667 4.175 0.691667 4.40833 0.925L7.325 3.84167C7.55833 4.075 7.55833 4.425 7.325 4.65833C7.09167 4.89167 6.74167 4.89167 6.50833 4.65833L4 2.15L1.49167 4.65833C1.375 4.775 1.25833 4.83333 1.08333 4.83333Z' fill='%23C4CADA'/%3e%3c/svg%3e\")}.grid-desc>.grid-sort-icon:after{background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5' fill='none'%3e%3cpath d='M4 4.24984C3.825 4.24984 3.70833 4.1915 3.59167 4.07484L0.675 1.15817C0.441667 0.924838 0.441667 0.574837 0.675 0.341504C0.908333 0.108171 1.25833 0.108171 1.49167 0.341504L4 2.84984L6.50833 0.341504C6.74167 0.108171 7.09167 0.108171 7.325 0.341504C7.55833 0.574837 7.55833 0.924838 7.325 1.15817L4.40833 4.07484C4.29167 4.1915 4.175 4.24984 4 4.24984Z' fill='%234B5675'/%3e%3c/svg%3e\")}.cdk-drag-preview{display:table;width:100%;background:#fff;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f}.cdk-drag-placeholder{opacity:.4;background-color:#f3f4f6}.cdk-drag-animating,.cdk-drop-list-dragging .cdk-drag{transition:transform .25s cubic-bezier(0,0,.2,1)}\n"] }]
|
|
3006
|
+
}], propDecorators: { draggable: [{
|
|
3007
|
+
type: Input
|
|
3008
|
+
}], columns: [{
|
|
3009
|
+
type: Input
|
|
3010
|
+
}], result: [{
|
|
3011
|
+
type: Input
|
|
3012
|
+
}], actions: [{
|
|
3013
|
+
type: Input
|
|
3014
|
+
}], actionClick: [{
|
|
3015
|
+
type: Output
|
|
3016
|
+
}], sortChange: [{
|
|
3017
|
+
type: Output
|
|
3018
|
+
}], dragDropChange: [{
|
|
3019
|
+
type: Output
|
|
3020
|
+
}], tableScrollContainer: [{
|
|
3021
|
+
type: ViewChild,
|
|
3022
|
+
args: ['tableScrollContainer', { static: false }]
|
|
3023
|
+
}] } });
|
|
3024
|
+
|
|
3025
|
+
class BkSelect {
|
|
3026
|
+
// --- Inputs ---
|
|
3027
|
+
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
3028
|
+
bindLabel = input('label', ...(ngDevMode ? [{ debugName: "bindLabel" }] : []));
|
|
3029
|
+
bindValue = input('', ...(ngDevMode ? [{ debugName: "bindValue" }] : []));
|
|
3030
|
+
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
3031
|
+
notFoundText = input('No items found', ...(ngDevMode ? [{ debugName: "notFoundText" }] : []));
|
|
3032
|
+
loadingText = input('Loading...', ...(ngDevMode ? [{ debugName: "loadingText" }] : []));
|
|
3033
|
+
clearAllText = input('Clear all', ...(ngDevMode ? [{ debugName: "clearAllText" }] : []));
|
|
3034
|
+
// iconSrc = input<string>('Clear all');
|
|
3035
|
+
iconAlt = 'icon';
|
|
3036
|
+
label = 'Label';
|
|
3037
|
+
required = false;
|
|
3038
|
+
iconSrc; // optional icon
|
|
3039
|
+
// Config
|
|
3040
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
3041
|
+
maxLabels = input(2, ...(ngDevMode ? [{ debugName: "maxLabels" }] : []));
|
|
3042
|
+
searchable = input(true, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
3043
|
+
clearable = input(true, ...(ngDevMode ? [{ debugName: "clearable" }] : []));
|
|
3044
|
+
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : []));
|
|
3045
|
+
disabled = model(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
3046
|
+
loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
3047
|
+
closeOnSelect = input(true, ...(ngDevMode ? [{ debugName: "closeOnSelect" }] : []));
|
|
3048
|
+
dropdownPosition = input('bottom', ...(ngDevMode ? [{ debugName: "dropdownPosition" }] : []));
|
|
3049
|
+
// 1. NEW INPUT: Toggle append-to-body behavior
|
|
3050
|
+
appendToBody = input(false, ...(ngDevMode ? [{ debugName: "appendToBody" }] : []));
|
|
3051
|
+
// --- Outputs ---
|
|
3052
|
+
open = output();
|
|
3053
|
+
close = output();
|
|
3054
|
+
focus = output();
|
|
3055
|
+
blur = output();
|
|
3056
|
+
search = output();
|
|
3057
|
+
clear = output();
|
|
3058
|
+
change = output();
|
|
3059
|
+
scrollToEnd = output();
|
|
3060
|
+
// --- Refs ---
|
|
3061
|
+
searchInput;
|
|
3062
|
+
optionsListContainer;
|
|
3063
|
+
optionsRef;
|
|
3064
|
+
controlWrapper;
|
|
3065
|
+
// --- State ---
|
|
3066
|
+
_value = null;
|
|
3067
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
3068
|
+
selectedOptions = signal([], ...(ngDevMode ? [{ debugName: "selectedOptions" }] : []));
|
|
3069
|
+
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
3070
|
+
markedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "markedIndex" }] : []));
|
|
3071
|
+
dropdownStyle = signal({
|
|
3072
|
+
left: '0px',
|
|
3073
|
+
width: 'auto'
|
|
3074
|
+
}, ...(ngDevMode ? [{ debugName: "dropdownStyle" }] : []));
|
|
3075
|
+
filteredItems = computed(() => {
|
|
3076
|
+
const term = this.searchTerm().toLowerCase();
|
|
3077
|
+
const list = this.items();
|
|
3078
|
+
if (!term || !this.searchable())
|
|
3079
|
+
return list;
|
|
3080
|
+
return list.filter(item => {
|
|
3081
|
+
const label = this.resolveLabel(item).toLowerCase();
|
|
3082
|
+
return label.includes(term);
|
|
3083
|
+
});
|
|
3084
|
+
}, ...(ngDevMode ? [{ debugName: "filteredItems" }] : []));
|
|
3085
|
+
isAllSelected = computed(() => {
|
|
3086
|
+
const filtered = this.filteredItems();
|
|
3087
|
+
const current = this.selectedOptions();
|
|
3088
|
+
if (!filtered.length)
|
|
3089
|
+
return false;
|
|
3090
|
+
return filtered.every(item => this.isItemSelected(item));
|
|
3091
|
+
}, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
|
|
3092
|
+
constructor() {
|
|
3093
|
+
effect(() => {
|
|
3094
|
+
const currentItems = this.items();
|
|
3095
|
+
if (currentItems.length > 0 && this._value !== null) {
|
|
3096
|
+
this.resolveSelectedOptions(this._value);
|
|
3097
|
+
}
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
// --- Helpers ---
|
|
3101
|
+
resolveLabel(item) {
|
|
3102
|
+
if (!item)
|
|
3103
|
+
return '';
|
|
3104
|
+
const labelProp = this.bindLabel();
|
|
3105
|
+
return labelProp && typeof item === 'object' ? item[labelProp] : String(item);
|
|
3106
|
+
}
|
|
3107
|
+
resolveValue(item) {
|
|
3108
|
+
const valueProp = this.bindValue();
|
|
3109
|
+
return valueProp && typeof item === 'object' ? item[valueProp] : item;
|
|
3110
|
+
}
|
|
3111
|
+
isItemSelected(item) {
|
|
3112
|
+
const current = this.selectedOptions();
|
|
3113
|
+
const compareFn = this.compareWith();
|
|
3114
|
+
const itemVal = this.resolveValue(item);
|
|
3115
|
+
return current.some(selected => compareFn(itemVal, this.resolveValue(selected)));
|
|
3116
|
+
}
|
|
3117
|
+
compareWith = input((a, b) => a === b, ...(ngDevMode ? [{ debugName: "compareWith" }] : []));
|
|
3118
|
+
// --- Actions ---
|
|
3119
|
+
toggleDropdown(event) {
|
|
3120
|
+
if (event) {
|
|
3121
|
+
event.preventDefault();
|
|
3122
|
+
event.stopPropagation();
|
|
3123
|
+
}
|
|
3124
|
+
if (this.disabled() || this.readonly())
|
|
3125
|
+
return;
|
|
3126
|
+
this.isOpen() ? this.closeDropdown() : this.openDropdown();
|
|
3127
|
+
}
|
|
3128
|
+
openDropdown() {
|
|
3129
|
+
if (this.isOpen())
|
|
3130
|
+
return;
|
|
3131
|
+
// Only calculate position if we are appending to body
|
|
3132
|
+
if (this.appendToBody()) {
|
|
3133
|
+
this.updatePosition();
|
|
3134
|
+
}
|
|
3135
|
+
this.isOpen.set(true);
|
|
3136
|
+
// this.markedIndex.set(0);
|
|
3137
|
+
this.open.emit();
|
|
3138
|
+
this.focus.emit();
|
|
3139
|
+
setTimeout(() => this.searchInput?.nativeElement.focus());
|
|
3140
|
+
}
|
|
3141
|
+
closeDropdown() {
|
|
3142
|
+
if (!this.isOpen())
|
|
3143
|
+
return;
|
|
3144
|
+
this.isOpen.set(false);
|
|
3145
|
+
this.searchTerm.set('');
|
|
3146
|
+
this.onTouched();
|
|
3147
|
+
this.close.emit();
|
|
3148
|
+
this.blur.emit();
|
|
3149
|
+
}
|
|
3150
|
+
getTop() {
|
|
3151
|
+
if (this.appendToBody()) {
|
|
3152
|
+
return this.dropdownStyle().top ?? null;
|
|
3153
|
+
}
|
|
3154
|
+
// NOT appendToBody
|
|
3155
|
+
return this.dropdownPosition() === 'bottom' ? '105%' : null;
|
|
3156
|
+
}
|
|
3157
|
+
getBottom() {
|
|
3158
|
+
if (this.appendToBody()) {
|
|
3159
|
+
return this.dropdownStyle().bottom ?? null;
|
|
3160
|
+
}
|
|
3161
|
+
// NOT appendToBody
|
|
3162
|
+
return this.dropdownPosition() === 'top' ? '105%' : null;
|
|
3163
|
+
}
|
|
3164
|
+
updatePosition() {
|
|
3165
|
+
const rect = this.controlWrapper.nativeElement.getBoundingClientRect();
|
|
3166
|
+
if (this.dropdownPosition() === 'bottom') {
|
|
3167
|
+
this.dropdownStyle.set({
|
|
3168
|
+
top: `${rect.bottom + 4}px`,
|
|
3169
|
+
bottom: undefined,
|
|
3170
|
+
left: `${rect.left}px`,
|
|
3171
|
+
width: `${rect.width}px`
|
|
3172
|
+
});
|
|
3173
|
+
}
|
|
3174
|
+
else {
|
|
3175
|
+
this.dropdownStyle.set({
|
|
3176
|
+
top: undefined,
|
|
3177
|
+
bottom: `${window.innerHeight - rect.top + 4}px`,
|
|
3178
|
+
left: `${rect.left}px`,
|
|
3179
|
+
width: `${rect.width}px`
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
// 2. FIXED: Removed ['$event'] argument
|
|
3184
|
+
onWindowEvents() {
|
|
3185
|
+
// Only close on scroll if we are in "fixed" mode (append to body)
|
|
3186
|
+
if (this.isOpen() && this.appendToBody()) {
|
|
3187
|
+
this.closeDropdown();
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
// ... (toggleSelectAll, handleSelection, removeOption, handleClear logic same as before) ...
|
|
3191
|
+
toggleSelectAll(event) {
|
|
3192
|
+
event.stopPropagation();
|
|
3193
|
+
event.preventDefault();
|
|
3194
|
+
const allSelected = this.isAllSelected();
|
|
3195
|
+
const filtered = this.filteredItems();
|
|
3196
|
+
let newSelection = [...this.selectedOptions()];
|
|
3197
|
+
const compareFn = this.compareWith();
|
|
3198
|
+
if (allSelected) {
|
|
3199
|
+
newSelection = newSelection.filter(sel => {
|
|
3200
|
+
const selVal = this.resolveValue(sel);
|
|
3201
|
+
return !filtered.some(fItem => compareFn(this.resolveValue(fItem), selVal));
|
|
3202
|
+
});
|
|
3203
|
+
}
|
|
3204
|
+
else {
|
|
3205
|
+
filtered.forEach(item => {
|
|
3206
|
+
if (!this.isItemSelected(item))
|
|
3207
|
+
newSelection.push(item);
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
3210
|
+
this.updateModel(newSelection);
|
|
3211
|
+
}
|
|
3212
|
+
handleSelection(item, event) {
|
|
3213
|
+
if (event)
|
|
3214
|
+
event.stopPropagation();
|
|
3215
|
+
if (this.multiple()) {
|
|
3216
|
+
const isSelected = this.isItemSelected(item);
|
|
3217
|
+
let newSelection = [...this.selectedOptions()];
|
|
3218
|
+
const compareFn = this.compareWith();
|
|
3219
|
+
if (isSelected) {
|
|
3220
|
+
const itemVal = this.resolveValue(item);
|
|
3221
|
+
newSelection = newSelection.filter(sel => !compareFn(this.resolveValue(sel), itemVal));
|
|
3222
|
+
}
|
|
3223
|
+
else {
|
|
3224
|
+
newSelection.push(item);
|
|
3225
|
+
}
|
|
3226
|
+
this.updateModel(newSelection);
|
|
3227
|
+
if (this.closeOnSelect())
|
|
3228
|
+
this.closeDropdown();
|
|
3229
|
+
}
|
|
3230
|
+
else {
|
|
3231
|
+
this.updateModel([item]);
|
|
3232
|
+
if (this.closeOnSelect())
|
|
3233
|
+
this.closeDropdown();
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
removeOption(item, event) {
|
|
3237
|
+
event.stopPropagation();
|
|
3238
|
+
const newSelection = this.selectedOptions().filter(i => i !== item);
|
|
3239
|
+
this.updateModel(newSelection);
|
|
3240
|
+
}
|
|
3241
|
+
handleClear(event) {
|
|
3242
|
+
event.stopPropagation();
|
|
3243
|
+
this.updateModel([]);
|
|
3244
|
+
this.clear.emit();
|
|
3245
|
+
}
|
|
3246
|
+
updateModel(items) {
|
|
3247
|
+
this.selectedOptions.set(items);
|
|
3248
|
+
if (this.multiple()) {
|
|
3249
|
+
const values = items.map(i => this.resolveValue(i));
|
|
3250
|
+
this._value = values;
|
|
3251
|
+
this.onChange(values);
|
|
3252
|
+
this.change.emit(values);
|
|
3253
|
+
}
|
|
3254
|
+
else {
|
|
3255
|
+
const item = items[0] || null;
|
|
3256
|
+
const value = item ? this.resolveValue(item) : null;
|
|
3257
|
+
this._value = value;
|
|
3258
|
+
this.onChange(value);
|
|
3259
|
+
this.change.emit(value);
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
onSearchInput(event) {
|
|
3263
|
+
const val = event.target.value;
|
|
3264
|
+
this.searchTerm.set(val);
|
|
3265
|
+
this.markedIndex.set(0);
|
|
3266
|
+
this.search.emit({ term: val, items: this.filteredItems() });
|
|
3267
|
+
}
|
|
3268
|
+
onKeyDown(event) {
|
|
3269
|
+
if (!this.isOpen())
|
|
3270
|
+
return;
|
|
3271
|
+
const list = this.filteredItems();
|
|
3272
|
+
const current = this.markedIndex();
|
|
3273
|
+
switch (event.key) {
|
|
3274
|
+
case 'ArrowDown':
|
|
3275
|
+
event.preventDefault();
|
|
3276
|
+
if (current < list.length - 1) {
|
|
3277
|
+
this.markedIndex.set(current + 1);
|
|
3278
|
+
this.scrollToMarked();
|
|
3279
|
+
}
|
|
3280
|
+
break;
|
|
3281
|
+
case 'ArrowUp':
|
|
3282
|
+
event.preventDefault();
|
|
3283
|
+
if (current > 0) {
|
|
3284
|
+
this.markedIndex.set(current - 1);
|
|
3285
|
+
this.scrollToMarked();
|
|
3286
|
+
}
|
|
3287
|
+
break;
|
|
3288
|
+
case 'Enter':
|
|
3289
|
+
event.preventDefault();
|
|
3290
|
+
if (current >= 0 && list[current])
|
|
3291
|
+
this.handleSelection(list[current]);
|
|
3292
|
+
break;
|
|
3293
|
+
case 'Escape':
|
|
3294
|
+
event.preventDefault();
|
|
3295
|
+
this.closeDropdown();
|
|
3296
|
+
break;
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
onScroll(event) {
|
|
3300
|
+
const target = event.target;
|
|
3301
|
+
if (target.scrollHeight - target.scrollTop <= target.clientHeight + 10) {
|
|
3302
|
+
this.scrollToEnd.emit();
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
scrollToMarked() {
|
|
3306
|
+
setTimeout(() => {
|
|
3307
|
+
const container = this.optionsListContainer?.nativeElement;
|
|
3308
|
+
const options = this.optionsRef?.toArray();
|
|
3309
|
+
const index = this.markedIndex();
|
|
3310
|
+
if (container && options && options[index]) {
|
|
3311
|
+
const el = options[index].nativeElement;
|
|
3312
|
+
if (el.offsetTop < container.scrollTop) {
|
|
3313
|
+
container.scrollTop = el.offsetTop;
|
|
3314
|
+
}
|
|
3315
|
+
else if ((el.offsetTop + el.clientHeight) > (container.scrollTop + container.clientHeight)) {
|
|
3316
|
+
container.scrollTop = (el.offsetTop + el.clientHeight) - container.clientHeight;
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3321
|
+
onChange = () => { };
|
|
3322
|
+
onTouched = () => { };
|
|
3323
|
+
writeValue(value) { this._value = value; this.resolveSelectedOptions(value); }
|
|
3324
|
+
registerOnChange(fn) { this.onChange = fn; }
|
|
3325
|
+
registerOnTouched(fn) { this.onTouched = fn; }
|
|
3326
|
+
setDisabledState(d) { this.disabled.set(d); }
|
|
3327
|
+
resolveSelectedOptions(val) {
|
|
3328
|
+
const list = this.items();
|
|
3329
|
+
if (!list.length)
|
|
3330
|
+
return;
|
|
3331
|
+
if (val === null || val === undefined) {
|
|
3332
|
+
this.selectedOptions.set([]);
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
const valArray = Array.isArray(val) ? val : [val];
|
|
3336
|
+
const bindVal = this.bindValue();
|
|
3337
|
+
const compare = this.compareWith();
|
|
3338
|
+
const matchedItems = list.filter(item => {
|
|
3339
|
+
const itemVal = bindVal ? item[bindVal] : item;
|
|
3340
|
+
return valArray.some(v => compare(v, itemVal));
|
|
3341
|
+
});
|
|
3342
|
+
this.selectedOptions.set(matchedItems);
|
|
3343
|
+
}
|
|
3344
|
+
el = inject(ElementRef);
|
|
3345
|
+
onClickOutside(e) {
|
|
3346
|
+
if (!this.el.nativeElement.contains(e.target))
|
|
3347
|
+
this.closeDropdown();
|
|
3348
|
+
}
|
|
3349
|
+
openFromLabel(event) {
|
|
3350
|
+
event.preventDefault();
|
|
3351
|
+
event.stopPropagation();
|
|
3352
|
+
if (this.disabled() || this.readonly())
|
|
3353
|
+
return;
|
|
3354
|
+
this.controlWrapper.nativeElement.focus();
|
|
3355
|
+
this.openDropdown();
|
|
3356
|
+
}
|
|
3357
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3358
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkSelect, isStandalone: true, selector: "bk-select", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, bindLabel: { classPropertyName: "bindLabel", publicName: "bindLabel", isSignal: true, isRequired: false, transformFunction: null }, bindValue: { classPropertyName: "bindValue", publicName: "bindValue", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, notFoundText: { classPropertyName: "notFoundText", publicName: "notFoundText", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, clearAllText: { classPropertyName: "clearAllText", publicName: "clearAllText", isSignal: true, isRequired: false, transformFunction: null }, iconAlt: { classPropertyName: "iconAlt", publicName: "iconAlt", isSignal: false, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, iconSrc: { classPropertyName: "iconSrc", publicName: "iconSrc", isSignal: false, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxLabels: { classPropertyName: "maxLabels", publicName: "maxLabels", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null }, dropdownPosition: { classPropertyName: "dropdownPosition", publicName: "dropdownPosition", isSignal: true, isRequired: false, transformFunction: null }, appendToBody: { classPropertyName: "appendToBody", publicName: "appendToBody", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", open: "open", close: "close", focus: "focus", blur: "blur", search: "search", clear: "clear", change: "change", scrollToEnd: "scrollToEnd" }, host: { listeners: { "window:scroll": "onWindowEvents()", "window:resize": "onWindowEvents()", "document:click": "onClickOutside($event)" } }, providers: [
|
|
3359
|
+
{
|
|
3360
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3361
|
+
useExisting: forwardRef(() => BkSelect),
|
|
3362
|
+
multi: true
|
|
3363
|
+
}
|
|
3364
|
+
], viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }, { propertyName: "optionsListContainer", first: true, predicate: ["optionsListContainer"], descendants: true }, { propertyName: "controlWrapper", first: true, predicate: ["controlWrapper"], descendants: true }, { propertyName: "optionsRef", predicate: ["optionsRef"], descendants: true }], ngImport: i0, template: "<div class=\"ng-select-container\">\r\n\r\n <label\r\n class=\"input-label\"\r\n (click)=\"openFromLabel($event)\">\r\n {{ label }}\r\n @if(required){\r\n <span class=\"input-label-required\">*</span>\r\n }\r\n </label>\r\n\r\n <div\r\n #controlWrapper\r\n class=\"ng-select-control\"\r\n [class.focused]=\"isOpen()\"\r\n [class.disabled]=\"disabled()\"\r\n (mousedown)=\"toggleDropdown($event)\"\r\n >\r\n <!-- Icon (Always visible if set) -->\r\n @if(iconSrc){\r\n <img [src]=\"iconSrc\" [alt]=\"iconAlt\" class=\"shrink-0\" />\r\n }\r\n <div class=\"ng-value-container\">\r\n @if (selectedOptions().length === 0)\r\n {\r\n <div class=\"ng-placeholder\">{{ placeholder() }}</div>\r\n }\r\n @if\r\n (multiple() && selectedOptions().length > 0) {\r\n <div class=\"ng-value-chips\">\r\n @for (opt of selectedOptions().slice(0, maxLabels()); track $index) {\r\n <div class=\"ng-value-chip\">\r\n <span class=\"ng-value-label\">{{ resolveLabel(opt) }}</span>\r\n <span class=\"ng-value-icon\" (mousedown)=\"removeOption(opt, $event)\">\u00D7</span>\r\n </div>\r\n }\r\n @if (selectedOptions().length > maxLabels()) {\r\n <div class=\"ng-value-chip remaining-count\"><span class=\"ng-value-label\">+{{ selectedOptions().length - maxLabels() }} more</span></div>\r\n }\r\n </div>\r\n }\r\n @if (!multiple() && selectedOptions().length > 0) {\r\n <div class=\"ng-value-label-single\">{{ resolveLabel(selectedOptions()[0]) }}</div>\r\n }\r\n </div>\r\n <div class=\"ng-actions\">\r\n @if (clearable() && selectedOptions().length > 0 && !disabled()) {\r\n <span class=\"ng-clear-wrapper\" (mousedown)=\"handleClear($event)\" title=\"Clear\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>\r\n </span>\r\n }\r\n <span class=\"ng-arrow-wrapper\" [class.open]=\"isOpen()\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (isOpen()) {\r\n <div\r\n #dropdownPanel\r\n class=\"custom-ng-dropdown-panel\"\r\n [attr.data-position]=\"dropdownPosition()\"\r\n (scroll)=\"onScroll($event)\"\r\n\r\n [style.position]=\"appendToBody() ? 'fixed' : 'absolute'\"\r\n [style.top]=\"getTop()\"\r\n [style.bottom]=\"getBottom()\"\r\n [style.left]=\"appendToBody() ? dropdownStyle().left : null\"\r\n [style.width]=\"appendToBody() ? dropdownStyle().width : '100%'\"\r\n >\r\n\r\n\r\n @if (searchable()) {\r\n <div class=\"ng-dropdown-search\">\r\n <div class=\"ng-search-wrapper\">\r\n <svg class=\"text-[#BBBDC5] mr-2\" xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"></circle><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line></svg>\r\n <input #searchInput type=\"text\" class=\"ng-search-input\" [value]=\"searchTerm()\" [placeholder]=\"'Search...'\" (input)=\"onSearchInput($event)\" (keydown)=\"onKeyDown($event)\" (click)=\"$event.stopPropagation()\">\r\n </div>\r\n </div>\r\n }\r\n @if (multiple() && filteredItems().length > 0) {\r\n <div class=\"ng-option select-all-option\" (mousedown)=\"toggleSelectAll($event)\">\r\n <div class=\"mr-2 flex items-center justify-center w-4 h-4 border border-gray-300 rounded bg-white\" [class.bg-blue-600]=\"isAllSelected()\" [class.border-blue-600]=\"isAllSelected()\">\r\n @if(isAllSelected()){ <svg class=\"text-white w-3 h-3\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"4\"><polyline points=\"20 6 9 17 4 12\"/></svg> }\r\n </div>\r\n <span class=\"font-semibold\">Select All</span>\r\n </div>\r\n }\r\n <div #optionsListContainer class=\"ng-options-list\">\r\n @if (loading()) { <div class=\"ng-option-disabled\">{{ loadingText() }}</div> }\r\n @else {\r\n @for (item of filteredItems(); track $index) {\r\n <div #optionsRef class=\"ng-option\" [class.selected]=\"isItemSelected(item)\" [class.marked]=\"$index === markedIndex()\" (click)=\"handleSelection(item, $event)\" (click)=\"markedIndex.set($index)\">\r\n @if (multiple()) {\r\n <div class=\"mr-2 flex items-center justify-center w-4 h-4 border border-gray-300 rounded bg-white\" [class.bg-blue-600]=\"isItemSelected(item)\" [class.border-blue-600]=\"isItemSelected(item)\">\r\n @if(isItemSelected(item)){ <svg class=\"text-white w-3 h-3\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"4\"><polyline points=\"20 6 9 17 4 12\"/></svg> }\r\n </div>\r\n }\r\n <span class=\"flex-1\">{{ resolveLabel(item) }}</span>\r\n @if (!multiple() && isItemSelected(item)) {\r\n <svg class=\"text-[#141414]\" width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><polyline points=\"20 6 9 17 4 12\"/></svg>\r\n }\r\n </div>\r\n }\r\n @if (filteredItems().length === 0) { <div class=\"ng-option-disabled\">{{ notFoundText() }}</div> }\r\n }\r\n </div>\r\n\r\n </div>\r\n }\r\n</div>\r\n", styles: [".ng-select-container{@apply relative w-full box-border;}.ng-select-control{@apply flex items-center justify-between gap-2 w-full bg-white border border-[#E3E3E7] rounded transition-all duration-200 px-3 py-[9.2px] cursor-pointer;}.ng-select-control.focused{@apply border-[#6B7080] shadow-none z-10;}.ng-select-control.disabled{@apply bg-[#F4F4F6] cursor-not-allowed opacity-60;}.ng-value-container{@apply flex flex-1 items-center flex-wrap gap-1 relative overflow-hidden h-full;}.ng-placeholder{@apply text-[#6B7080] font-normal text-sm truncate w-full pointer-events-none;}.ng-value-label-single{@apply font-normal text-sm text-[#141414] truncate w-full;}.ng-value-chips{@apply flex flex-wrap gap-1 w-full;}.ng-value-chip{@apply flex items-center bg-gray-100 border border-gray-200 rounded px-2 py-0.5 max-w-full;}.ng-value-chip .ng-value-label{@apply text-[#15191E] truncate max-w-[150px];}.ng-value-chip .ng-value-icon{@apply ml-1 text-gray-500 hover:text-red-500 cursor-pointer text-base font-bold leading-none px-1;}.ng-actions{@apply flex items-center gap-0.5 flex-shrink-0;}.ng-clear-wrapper{@apply text-gray-400 hover:text-red-500 cursor-pointer;}.ng-arrow-wrapper{@apply text-gray-400 transition-transform duration-200;}.ng-arrow-wrapper.open{@apply rotate-180;}.custom-ng-dropdown-panel{@apply absolute left-0 w-full bg-white border border-[#E3E3E7] rounded-xl shadow-lg z-[99] overflow-hidden cursor-default;}.ng-dropdown-search{@apply px-2 pt-2;}.ng-search-wrapper{@apply flex items-center border border-[#E3E3E7] rounded-md px-3 py-[7px] bg-white transition-colors focus-within:border-[#E3E3E7];}.ng-search-input{@apply w-full outline-none font-normal text-sm text-[#141414] placeholder-[#A1A3AE] bg-transparent;}.ng-options-list{@apply max-h-60 overflow-auto relative p-2.5 mb-3.5 flex flex-col gap-0.5;}.ng-option{@apply flex items-center p-2.5 cursor-pointer transition-colors font-normal text-sm text-[#141414];}.ng-option:hover,.ng-option.marked,.ng-option.selected{@apply bg-[#F8F8F8] rounded-md;}.ng-option-disabled{@apply px-3 py-2 text-gray-400 cursor-default;}.ng-value-chip.remaining-count{@apply bg-gray-200 text-gray-600 font-semibold cursor-default;}.select-all-option{@apply sticky top-0 z-20 flex items-center px-3 py-2 cursor-pointer border-b border-[#E3E6EE] bg-gray-50 text-[#15191E];}.select-all-option:hover{@apply bg-gray-100;}.custom-ng-dropdown-panel[data-position=top]{margin-top:0;margin-bottom:4px}.input-label{@apply text-sm font-medium text-[#141414] tracking-[-.28px] mb-1.5 inline-block;}.input-label-required{@apply text-[#E7000B];}::-webkit-scrollbar{width:10px}::-webkit-scrollbar-track{background:transparent;border-radius:8px;width:8px}::-webkit-scrollbar-thumb{background:#d6d7dc;border-radius:8px;transition:.3s ease-in-out}::-webkit-scrollbar-thumb:hover{background:#909090}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
|
|
3365
|
+
}
|
|
3366
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkSelect, decorators: [{
|
|
3367
|
+
type: Component,
|
|
3368
|
+
args: [{ selector: 'bk-select', standalone: true, imports: [CommonModule, FormsModule], providers: [
|
|
3369
|
+
{
|
|
3370
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3371
|
+
useExisting: forwardRef(() => BkSelect),
|
|
3372
|
+
multi: true
|
|
3373
|
+
}
|
|
3374
|
+
], template: "<div class=\"ng-select-container\">\r\n\r\n <label\r\n class=\"input-label\"\r\n (click)=\"openFromLabel($event)\">\r\n {{ label }}\r\n @if(required){\r\n <span class=\"input-label-required\">*</span>\r\n }\r\n </label>\r\n\r\n <div\r\n #controlWrapper\r\n class=\"ng-select-control\"\r\n [class.focused]=\"isOpen()\"\r\n [class.disabled]=\"disabled()\"\r\n (mousedown)=\"toggleDropdown($event)\"\r\n >\r\n <!-- Icon (Always visible if set) -->\r\n @if(iconSrc){\r\n <img [src]=\"iconSrc\" [alt]=\"iconAlt\" class=\"shrink-0\" />\r\n }\r\n <div class=\"ng-value-container\">\r\n @if (selectedOptions().length === 0)\r\n {\r\n <div class=\"ng-placeholder\">{{ placeholder() }}</div>\r\n }\r\n @if\r\n (multiple() && selectedOptions().length > 0) {\r\n <div class=\"ng-value-chips\">\r\n @for (opt of selectedOptions().slice(0, maxLabels()); track $index) {\r\n <div class=\"ng-value-chip\">\r\n <span class=\"ng-value-label\">{{ resolveLabel(opt) }}</span>\r\n <span class=\"ng-value-icon\" (mousedown)=\"removeOption(opt, $event)\">\u00D7</span>\r\n </div>\r\n }\r\n @if (selectedOptions().length > maxLabels()) {\r\n <div class=\"ng-value-chip remaining-count\"><span class=\"ng-value-label\">+{{ selectedOptions().length - maxLabels() }} more</span></div>\r\n }\r\n </div>\r\n }\r\n @if (!multiple() && selectedOptions().length > 0) {\r\n <div class=\"ng-value-label-single\">{{ resolveLabel(selectedOptions()[0]) }}</div>\r\n }\r\n </div>\r\n <div class=\"ng-actions\">\r\n @if (clearable() && selectedOptions().length > 0 && !disabled()) {\r\n <span class=\"ng-clear-wrapper\" (mousedown)=\"handleClear($event)\" title=\"Clear\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>\r\n </span>\r\n }\r\n <span class=\"ng-arrow-wrapper\" [class.open]=\"isOpen()\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (isOpen()) {\r\n <div\r\n #dropdownPanel\r\n class=\"custom-ng-dropdown-panel\"\r\n [attr.data-position]=\"dropdownPosition()\"\r\n (scroll)=\"onScroll($event)\"\r\n\r\n [style.position]=\"appendToBody() ? 'fixed' : 'absolute'\"\r\n [style.top]=\"getTop()\"\r\n [style.bottom]=\"getBottom()\"\r\n [style.left]=\"appendToBody() ? dropdownStyle().left : null\"\r\n [style.width]=\"appendToBody() ? dropdownStyle().width : '100%'\"\r\n >\r\n\r\n\r\n @if (searchable()) {\r\n <div class=\"ng-dropdown-search\">\r\n <div class=\"ng-search-wrapper\">\r\n <svg class=\"text-[#BBBDC5] mr-2\" xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"></circle><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line></svg>\r\n <input #searchInput type=\"text\" class=\"ng-search-input\" [value]=\"searchTerm()\" [placeholder]=\"'Search...'\" (input)=\"onSearchInput($event)\" (keydown)=\"onKeyDown($event)\" (click)=\"$event.stopPropagation()\">\r\n </div>\r\n </div>\r\n }\r\n @if (multiple() && filteredItems().length > 0) {\r\n <div class=\"ng-option select-all-option\" (mousedown)=\"toggleSelectAll($event)\">\r\n <div class=\"mr-2 flex items-center justify-center w-4 h-4 border border-gray-300 rounded bg-white\" [class.bg-blue-600]=\"isAllSelected()\" [class.border-blue-600]=\"isAllSelected()\">\r\n @if(isAllSelected()){ <svg class=\"text-white w-3 h-3\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"4\"><polyline points=\"20 6 9 17 4 12\"/></svg> }\r\n </div>\r\n <span class=\"font-semibold\">Select All</span>\r\n </div>\r\n }\r\n <div #optionsListContainer class=\"ng-options-list\">\r\n @if (loading()) { <div class=\"ng-option-disabled\">{{ loadingText() }}</div> }\r\n @else {\r\n @for (item of filteredItems(); track $index) {\r\n <div #optionsRef class=\"ng-option\" [class.selected]=\"isItemSelected(item)\" [class.marked]=\"$index === markedIndex()\" (click)=\"handleSelection(item, $event)\" (click)=\"markedIndex.set($index)\">\r\n @if (multiple()) {\r\n <div class=\"mr-2 flex items-center justify-center w-4 h-4 border border-gray-300 rounded bg-white\" [class.bg-blue-600]=\"isItemSelected(item)\" [class.border-blue-600]=\"isItemSelected(item)\">\r\n @if(isItemSelected(item)){ <svg class=\"text-white w-3 h-3\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"4\"><polyline points=\"20 6 9 17 4 12\"/></svg> }\r\n </div>\r\n }\r\n <span class=\"flex-1\">{{ resolveLabel(item) }}</span>\r\n @if (!multiple() && isItemSelected(item)) {\r\n <svg class=\"text-[#141414]\" width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><polyline points=\"20 6 9 17 4 12\"/></svg>\r\n }\r\n </div>\r\n }\r\n @if (filteredItems().length === 0) { <div class=\"ng-option-disabled\">{{ notFoundText() }}</div> }\r\n }\r\n </div>\r\n\r\n </div>\r\n }\r\n</div>\r\n", styles: [".ng-select-container{@apply relative w-full box-border;}.ng-select-control{@apply flex items-center justify-between gap-2 w-full bg-white border border-[#E3E3E7] rounded transition-all duration-200 px-3 py-[9.2px] cursor-pointer;}.ng-select-control.focused{@apply border-[#6B7080] shadow-none z-10;}.ng-select-control.disabled{@apply bg-[#F4F4F6] cursor-not-allowed opacity-60;}.ng-value-container{@apply flex flex-1 items-center flex-wrap gap-1 relative overflow-hidden h-full;}.ng-placeholder{@apply text-[#6B7080] font-normal text-sm truncate w-full pointer-events-none;}.ng-value-label-single{@apply font-normal text-sm text-[#141414] truncate w-full;}.ng-value-chips{@apply flex flex-wrap gap-1 w-full;}.ng-value-chip{@apply flex items-center bg-gray-100 border border-gray-200 rounded px-2 py-0.5 max-w-full;}.ng-value-chip .ng-value-label{@apply text-[#15191E] truncate max-w-[150px];}.ng-value-chip .ng-value-icon{@apply ml-1 text-gray-500 hover:text-red-500 cursor-pointer text-base font-bold leading-none px-1;}.ng-actions{@apply flex items-center gap-0.5 flex-shrink-0;}.ng-clear-wrapper{@apply text-gray-400 hover:text-red-500 cursor-pointer;}.ng-arrow-wrapper{@apply text-gray-400 transition-transform duration-200;}.ng-arrow-wrapper.open{@apply rotate-180;}.custom-ng-dropdown-panel{@apply absolute left-0 w-full bg-white border border-[#E3E3E7] rounded-xl shadow-lg z-[99] overflow-hidden cursor-default;}.ng-dropdown-search{@apply px-2 pt-2;}.ng-search-wrapper{@apply flex items-center border border-[#E3E3E7] rounded-md px-3 py-[7px] bg-white transition-colors focus-within:border-[#E3E3E7];}.ng-search-input{@apply w-full outline-none font-normal text-sm text-[#141414] placeholder-[#A1A3AE] bg-transparent;}.ng-options-list{@apply max-h-60 overflow-auto relative p-2.5 mb-3.5 flex flex-col gap-0.5;}.ng-option{@apply flex items-center p-2.5 cursor-pointer transition-colors font-normal text-sm text-[#141414];}.ng-option:hover,.ng-option.marked,.ng-option.selected{@apply bg-[#F8F8F8] rounded-md;}.ng-option-disabled{@apply px-3 py-2 text-gray-400 cursor-default;}.ng-value-chip.remaining-count{@apply bg-gray-200 text-gray-600 font-semibold cursor-default;}.select-all-option{@apply sticky top-0 z-20 flex items-center px-3 py-2 cursor-pointer border-b border-[#E3E6EE] bg-gray-50 text-[#15191E];}.select-all-option:hover{@apply bg-gray-100;}.custom-ng-dropdown-panel[data-position=top]{margin-top:0;margin-bottom:4px}.input-label{@apply text-sm font-medium text-[#141414] tracking-[-.28px] mb-1.5 inline-block;}.input-label-required{@apply text-[#E7000B];}::-webkit-scrollbar{width:10px}::-webkit-scrollbar-track{background:transparent;border-radius:8px;width:8px}::-webkit-scrollbar-thumb{background:#d6d7dc;border-radius:8px;transition:.3s ease-in-out}::-webkit-scrollbar-thumb:hover{background:#909090}\n"] }]
|
|
3375
|
+
}], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], bindLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindLabel", required: false }] }], bindValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindValue", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], notFoundText: [{ type: i0.Input, args: [{ isSignal: true, alias: "notFoundText", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], clearAllText: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearAllText", required: false }] }], iconAlt: [{
|
|
3376
|
+
type: Input
|
|
3377
|
+
}], label: [{
|
|
3378
|
+
type: Input
|
|
3379
|
+
}], required: [{
|
|
3380
|
+
type: Input
|
|
3381
|
+
}], iconSrc: [{
|
|
3382
|
+
type: Input
|
|
3383
|
+
}], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLabels", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], dropdownPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropdownPosition", required: false }] }], appendToBody: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendToBody", required: false }] }], open: [{ type: i0.Output, args: ["open"] }], close: [{ type: i0.Output, args: ["close"] }], focus: [{ type: i0.Output, args: ["focus"] }], blur: [{ type: i0.Output, args: ["blur"] }], search: [{ type: i0.Output, args: ["search"] }], clear: [{ type: i0.Output, args: ["clear"] }], change: [{ type: i0.Output, args: ["change"] }], scrollToEnd: [{ type: i0.Output, args: ["scrollToEnd"] }], searchInput: [{
|
|
3384
|
+
type: ViewChild,
|
|
3385
|
+
args: ['searchInput']
|
|
3386
|
+
}], optionsListContainer: [{
|
|
3387
|
+
type: ViewChild,
|
|
3388
|
+
args: ['optionsListContainer']
|
|
3389
|
+
}], optionsRef: [{
|
|
3390
|
+
type: ViewChildren,
|
|
3391
|
+
args: ['optionsRef']
|
|
3392
|
+
}], controlWrapper: [{
|
|
3393
|
+
type: ViewChild,
|
|
3394
|
+
args: ['controlWrapper']
|
|
3395
|
+
}], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], onWindowEvents: [{
|
|
3396
|
+
type: HostListener,
|
|
3397
|
+
args: ['window:scroll']
|
|
3398
|
+
}, {
|
|
3399
|
+
type: HostListener,
|
|
3400
|
+
args: ['window:resize']
|
|
3401
|
+
}], onClickOutside: [{
|
|
3402
|
+
type: HostListener,
|
|
3403
|
+
args: ['document:click', ['$event']]
|
|
3404
|
+
}] } });
|
|
3405
|
+
|
|
3406
|
+
class BkInput {
|
|
3407
|
+
// =================== Inputs (all your original ones) ===================
|
|
3408
|
+
id;
|
|
3409
|
+
name;
|
|
3410
|
+
mask = null;
|
|
3411
|
+
autoComplete = 'off';
|
|
3412
|
+
label = '';
|
|
3413
|
+
placeholder = 'stephend@i2cinc.com';
|
|
3414
|
+
hint = '';
|
|
3415
|
+
required = false;
|
|
3416
|
+
type = 'text';
|
|
3417
|
+
value = '';
|
|
3418
|
+
hasError = false;
|
|
3419
|
+
showErrorIcon = false;
|
|
3420
|
+
errorMessage = '';
|
|
3421
|
+
disabled = false;
|
|
3422
|
+
tabIndex = null;
|
|
3423
|
+
readOnly = false;
|
|
3424
|
+
autoCapitalize = null;
|
|
3425
|
+
inputMode = null;
|
|
3426
|
+
iconSrc;
|
|
3427
|
+
iconAlt = 'icon';
|
|
3428
|
+
showIcon = true;
|
|
3429
|
+
phone = false;
|
|
3430
|
+
countryCode = 'US';
|
|
3431
|
+
countryOptions = [
|
|
3432
|
+
{ code: 'US', name: 'US', mask: '(000) 000-0000', prefix: '+1 ', placeholder: '(000) 000-0000' },
|
|
3433
|
+
{ code: 'MT', name: 'MT', mask: '(000) 000-0000', prefix: '+356 ', placeholder: '(000) 000-0000' },
|
|
3434
|
+
{ code: 'PL', name: 'PL', mask: '(000) 000-0000', prefix: '+48 ', placeholder: '(000) 000-0000' },
|
|
3435
|
+
{ code: 'CH', name: 'CH', mask: '(000) 000-0000', prefix: '+41 ', placeholder: '(000) 000-0000' },
|
|
3436
|
+
{ code: 'TR', name: 'TR', mask: '(000) 000-0000', prefix: '+90 ', placeholder: '(000) 000-0000' },
|
|
3437
|
+
{ code: 'UG', name: 'UG', mask: '(000) 000-0000', prefix: '+256 ', placeholder: '(000) 000-0000' },
|
|
3438
|
+
{ code: 'ZM', name: 'ZM', mask: '(000) 000-0000', prefix: '+260 ', placeholder: '(000) 000-0000' }
|
|
3439
|
+
];
|
|
3440
|
+
selectedCountry = this.countryOptions[0];
|
|
3441
|
+
iconOrientation = 'left';
|
|
3442
|
+
password = false;
|
|
3443
|
+
showPassword = false;
|
|
3444
|
+
pattern;
|
|
3445
|
+
max = null;
|
|
3446
|
+
min = null;
|
|
3447
|
+
step = null;
|
|
3448
|
+
maxlength = null;
|
|
3449
|
+
minlength = null;
|
|
3450
|
+
// =================== ViewChild ===================
|
|
3451
|
+
dropdownRef;
|
|
3452
|
+
selectRef;
|
|
3453
|
+
inputField;
|
|
3454
|
+
// =================== Internal State ===================
|
|
3455
|
+
isFocused = false;
|
|
3456
|
+
inputValue = '';
|
|
3457
|
+
isDropdownOpen = false;
|
|
3458
|
+
// =================== Output Emitter ===================
|
|
3459
|
+
input = new EventEmitter();
|
|
3460
|
+
change = new EventEmitter();
|
|
3461
|
+
focus = new EventEmitter();
|
|
3462
|
+
blur = new EventEmitter();
|
|
3463
|
+
clicked = new EventEmitter();
|
|
3464
|
+
get placeHolderText() {
|
|
3465
|
+
if (this.phone) {
|
|
3466
|
+
const country = this.countryOptions.find(c => c.code === this.countryCode);
|
|
3467
|
+
return country?.placeholder || '';
|
|
3468
|
+
}
|
|
3469
|
+
return this.placeholder;
|
|
3470
|
+
}
|
|
3471
|
+
get maskValue() {
|
|
3472
|
+
if (this.mask)
|
|
3473
|
+
return this.mask;
|
|
3474
|
+
if (this.phone) {
|
|
3475
|
+
const country = this.countryOptions.find(c => c.code === this.countryCode);
|
|
3476
|
+
return country?.mask || '';
|
|
3477
|
+
}
|
|
3478
|
+
return '';
|
|
3479
|
+
}
|
|
3480
|
+
get maskPrefixValue() {
|
|
3481
|
+
if (this.phone) {
|
|
3482
|
+
const country = this.countryOptions.find(c => c.code === this.countryCode);
|
|
3483
|
+
return country?.prefix || '';
|
|
3484
|
+
}
|
|
3485
|
+
return '';
|
|
3486
|
+
}
|
|
3487
|
+
// =================== CVA Callbacks ===================
|
|
3488
|
+
onChange = (value) => { };
|
|
3489
|
+
onTouched = () => { };
|
|
3490
|
+
writeValue(value) {
|
|
3491
|
+
this.value = value || '';
|
|
3492
|
+
this.inputValue = this.value;
|
|
3493
|
+
}
|
|
3494
|
+
registerOnChange(fn) {
|
|
3495
|
+
this.onChange = fn;
|
|
3496
|
+
}
|
|
3497
|
+
registerOnTouched(fn) {
|
|
3498
|
+
this.onTouched = fn;
|
|
3499
|
+
}
|
|
3500
|
+
setDisabledState(isDisabled) {
|
|
3501
|
+
this.disabled = isDisabled;
|
|
3502
|
+
}
|
|
3503
|
+
// =================== Lifecycle ===================
|
|
3504
|
+
closeAllDropdownsHandler = () => {
|
|
3505
|
+
if (this.phone && this.isDropdownOpen)
|
|
3506
|
+
this.isDropdownOpen = false;
|
|
3507
|
+
};
|
|
3508
|
+
ngOnInit() {
|
|
3509
|
+
if (this.value)
|
|
3510
|
+
this.inputValue = this.value;
|
|
3511
|
+
if (this.password && this.type !== 'password')
|
|
3512
|
+
this.type = 'password';
|
|
3513
|
+
if (this.phone) {
|
|
3514
|
+
const country = this.countryOptions.find(c => c.code === this.countryCode);
|
|
3515
|
+
if (country)
|
|
3516
|
+
this.selectedCountry = country;
|
|
3517
|
+
document.addEventListener('closeAllPhoneDropdowns', this.closeAllDropdownsHandler);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
ngOnDestroy() {
|
|
3521
|
+
if (this.phone) {
|
|
3522
|
+
document.removeEventListener('closeAllPhoneDropdowns', this.closeAllDropdownsHandler);
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
// =================== Host Listener ===================
|
|
3526
|
+
onDocumentClick(event) {
|
|
3527
|
+
if (this.phone && this.isDropdownOpen) {
|
|
3528
|
+
const target = event.target;
|
|
3529
|
+
if (this.selectRef?.nativeElement && this.dropdownRef?.nativeElement) {
|
|
3530
|
+
const clickedInside = this.selectRef.nativeElement.contains(target) ||
|
|
3531
|
+
this.dropdownRef.nativeElement.contains(target);
|
|
3532
|
+
if (!clickedInside)
|
|
3533
|
+
this.isDropdownOpen = false;
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
// =================== Event Handlers ===================
|
|
3538
|
+
handleFocus(event) {
|
|
3539
|
+
this.focus.emit(event);
|
|
3540
|
+
if (!this.disabled)
|
|
3541
|
+
this.isFocused = true;
|
|
3542
|
+
}
|
|
3543
|
+
handleBlur(event) {
|
|
3544
|
+
this.isFocused = false;
|
|
3545
|
+
this.onTouched();
|
|
3546
|
+
this.blur.emit(event);
|
|
3547
|
+
}
|
|
3548
|
+
handleInput(event) {
|
|
3549
|
+
const val = event.target.value;
|
|
3550
|
+
this.inputValue = val;
|
|
3551
|
+
this.value = val; // update CVA value
|
|
3552
|
+
this.onChange(val); // propagate to parent form
|
|
3553
|
+
this.input.emit(event); // emit raw event
|
|
3554
|
+
}
|
|
3555
|
+
handleClicked() {
|
|
3556
|
+
this.clicked.emit(true);
|
|
3557
|
+
}
|
|
3558
|
+
handleChange(event) {
|
|
3559
|
+
this.change.emit(event); // emit raw change event
|
|
3560
|
+
}
|
|
3561
|
+
toggleDropdown(event) {
|
|
3562
|
+
if (!this.disabled && this.phone) {
|
|
3563
|
+
if (!this.isDropdownOpen) {
|
|
3564
|
+
document.dispatchEvent(new CustomEvent('closeAllPhoneDropdowns'));
|
|
3565
|
+
setTimeout(() => (this.isDropdownOpen = true), 0);
|
|
3566
|
+
}
|
|
3567
|
+
else {
|
|
3568
|
+
this.isDropdownOpen = false;
|
|
3569
|
+
}
|
|
3570
|
+
event?.stopPropagation();
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
selectCountry(country) {
|
|
3574
|
+
const oldCountry = this.selectedCountry;
|
|
3575
|
+
this.selectedCountry = country;
|
|
3576
|
+
this.countryCode = country.code;
|
|
3577
|
+
this.isDropdownOpen = false;
|
|
3578
|
+
const currentRaw = this.inputField?.nativeElement?.value || this.inputValue;
|
|
3579
|
+
let newPhone = '';
|
|
3580
|
+
if (currentRaw && currentRaw.trim() !== '') {
|
|
3581
|
+
// Input has some value → replace old prefix
|
|
3582
|
+
if (oldCountry?.prefix && oldCountry.code !== country.code) {
|
|
3583
|
+
const oldPrefixEscaped = oldCountry.prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3584
|
+
const regex = new RegExp(`^${oldPrefixEscaped}\\s?`);
|
|
3585
|
+
const oldMaskedValue = currentRaw.replace(regex, '');
|
|
3586
|
+
newPhone = country.prefix + oldMaskedValue;
|
|
3587
|
+
}
|
|
3588
|
+
else {
|
|
3589
|
+
newPhone = country.prefix + currentRaw;
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
else {
|
|
3593
|
+
// Input is empty → show only the new prefix OR empty
|
|
3594
|
+
newPhone = ''; // optionally: newPhone = country.prefix;
|
|
3595
|
+
}
|
|
3596
|
+
this.inputValue = newPhone;
|
|
3597
|
+
this.value = newPhone;
|
|
3598
|
+
this.onChange(newPhone);
|
|
3599
|
+
setTimeout(() => {
|
|
3600
|
+
if (this.inputField?.nativeElement) {
|
|
3601
|
+
this.inputField.nativeElement.value = newPhone;
|
|
3602
|
+
this.inputField.nativeElement.dispatchEvent(new Event('input', { bubbles: true }));
|
|
3603
|
+
}
|
|
3604
|
+
}, 0);
|
|
3605
|
+
}
|
|
3606
|
+
togglePasswordVisibility(event) {
|
|
3607
|
+
if (!this.disabled && this.password) {
|
|
3608
|
+
event.preventDefault();
|
|
3609
|
+
event.stopPropagation();
|
|
3610
|
+
this.showPassword = !this.showPassword;
|
|
3611
|
+
this.type = this.showPassword ? 'text' : 'password';
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
handleIconClick(event) {
|
|
3615
|
+
if (this.disabled || this.readOnly) {
|
|
3616
|
+
event.preventDefault();
|
|
3617
|
+
event.stopPropagation();
|
|
3618
|
+
return;
|
|
3619
|
+
}
|
|
3620
|
+
this.clicked.emit(true);
|
|
3621
|
+
}
|
|
3622
|
+
// =================== Getters ===================
|
|
3623
|
+
get isFilled() {
|
|
3624
|
+
return this.inputValue.length > 0;
|
|
3625
|
+
}
|
|
3626
|
+
get inputState() {
|
|
3627
|
+
if (this.disabled)
|
|
3628
|
+
return 'disabled';
|
|
3629
|
+
if (this.hasError)
|
|
3630
|
+
return 'error';
|
|
3631
|
+
if (this.isFocused || this.isDropdownOpen)
|
|
3632
|
+
return 'focused';
|
|
3633
|
+
if (this.isFilled)
|
|
3634
|
+
return 'filled';
|
|
3635
|
+
return 'default';
|
|
3636
|
+
}
|
|
3637
|
+
get currentInputType() {
|
|
3638
|
+
if (this.password)
|
|
3639
|
+
return this.showPassword ? 'text' : 'password';
|
|
3640
|
+
return this.type;
|
|
3641
|
+
}
|
|
3642
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3643
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkInput, isStandalone: true, selector: "bk-input", inputs: { id: "id", name: "name", mask: "mask", autoComplete: "autoComplete", label: "label", placeholder: "placeholder", hint: "hint", required: "required", type: "type", value: "value", hasError: "hasError", showErrorIcon: "showErrorIcon", errorMessage: "errorMessage", disabled: "disabled", tabIndex: "tabIndex", readOnly: "readOnly", autoCapitalize: "autoCapitalize", inputMode: "inputMode", iconSrc: "iconSrc", iconAlt: "iconAlt", showIcon: "showIcon", phone: "phone", countryCode: "countryCode", countryOptions: "countryOptions", iconOrientation: "iconOrientation", password: "password", showPassword: "showPassword", pattern: "pattern", max: "max", min: "min", step: "step", maxlength: "maxlength", minlength: "minlength" }, outputs: { input: "input", change: "change", focus: "focus", blur: "blur", clicked: "clicked" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, providers: [
|
|
3644
|
+
provideNgxMask(),
|
|
3645
|
+
{
|
|
3646
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3647
|
+
useExisting: forwardRef(() => BkInput),
|
|
3648
|
+
multi: true
|
|
3649
|
+
}
|
|
3650
|
+
], viewQueries: [{ propertyName: "dropdownRef", first: true, predicate: ["dropdownRef"], descendants: true }, { propertyName: "selectRef", first: true, predicate: ["selectRef"], descendants: true }, { propertyName: "inputField", first: true, predicate: ["inputField"], descendants: true }], ngImport: i0, template: "<div class=\"input-container\">\r\n @if(label){\r\n <label [for]=\"id\" class=\"input-label\">\r\n {{ label }}\r\n @if(required){\r\n <span class=\"input-label-required\">*</span>\r\n }\r\n </label>\r\n }\r\n\r\n <div class=\"input-wrapper\" [ngClass]=\"{\r\n 'input-wrapper--url': type === 'url',\r\n 'input-wrapper--phone': phone,\r\n 'input-wrapper--password': password,\r\n 'input-wrapper--icon': iconSrc && showIcon\r\n }\">\r\n\r\n <input\r\n #inputField\r\n [type]=\"currentInputType\"\r\n [id]=\"id\"\r\n [name]=\"name\"\r\n [disabled]=\"disabled\"\r\n [tabindex]=\"tabIndex\"\r\n [readOnly]=\"readOnly\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [autocomplete]=\"autoComplete\"\r\n [autocapitalize]=\"autoCapitalize\"\r\n\r\n [attr.max]=\"max\"\r\n [attr.min]=\"min\"\r\n [attr.step]=\"step\"\r\n\r\n [placeholder]=\"placeHolderText\"\r\n [pattern]=\"pattern\"\r\n [autocomplete]=\"autoComplete\"\r\n [value]=\"inputValue\"\r\n\r\n\r\n\r\n [mask]=\"maskValue\"\r\n [prefix]=\"maskPrefixValue\"\r\n [showMaskTyped]=\"false\"\r\n [dropSpecialCharacters]=\"false\"\r\n (change)=\"handleChange($event)\"\r\n (input)=\"handleInput($event)\"\r\n (focus)=\"handleFocus($event)\"\r\n (blur)=\"handleBlur($event)\"\r\n class=\"input-field\"\r\n\r\n [ngClass]=\"{\r\n 'input-field--url': type === 'url',\r\n 'input-field--phone': phone,\r\n 'input-field--icon-left': iconSrc && showIcon && iconOrientation === 'left',\r\n 'input-field--icon-right': iconSrc && showIcon && iconOrientation === 'right',\r\n 'input-field--password': password,\r\n 'input-field--default': inputState === 'default',\r\n 'input-field--focused': inputState === 'focused',\r\n 'input-field--filled': inputState === 'filled',\r\n 'input-field--error': inputState === 'error',\r\n 'input-field--disabled': inputState === 'disabled'\r\n }\">\r\n\r\n @if(iconSrc && showIcon){\r\n <img (click)=\"handleIconClick($event)\" [src]=\"iconSrc\" [alt]=\"iconAlt\" [ngClass]=\"{\r\n 'input-search-icon--left': iconOrientation === 'left',\r\n 'input-search-icon--right': iconOrientation === 'right',\r\n 'cursor-pointer': !disabled && !readOnly\r\n }\" class=\"input-search-icon\">\r\n }\r\n\r\n @if(showErrorIcon){\r\n <img src=\"../../assets/images/icons/global/info-circle.svg\" class=\"input-search-icon input-search-icon--right\">\r\n }\r\n\r\n @if(password){\r\n <button type=\"button\" (click)=\"togglePasswordVisibility($event)\" class=\"input-password-toggle\" [disabled]=\"disabled\" tabindex=\"-1\">\r\n <img [src]=\"showPassword ? '../../assets/images/icons/global/eye-slash-icon.svg' : '../../assets/images/icons/global/eye-icon.svg'\" [alt]=\"showPassword ? 'Hide password' : 'Show password'\" class=\"input-password-icon\">\r\n </button>\r\n }\r\n\r\n @if(phone){\r\n <div #selectRef class=\"input-phone-selector\" [ngClass]=\"{\r\n 'input-phone-selector--default': inputState === 'default',\r\n 'input-phone-selector--focused': inputState === 'focused',\r\n 'input-phone-selector--filled': inputState === 'filled',\r\n 'input-phone-selector--error': inputState === 'error',\r\n 'input-phone-selector--disabled': inputState === 'disabled'\r\n }\" (click)=\"toggleDropdown($event)\">\r\n <span class=\"input-phone-selector-text\">{{ selectedCountry.name }}</span>\r\n <img src=\"../../assets/images/icons/global/input-arrow-down.svg\" alt=\"Dropdown\" class=\"input-phone-selector-arrow\" [ngClass]=\"{'input-phone-selector-arrow--open': isDropdownOpen}\">\r\n </div>\r\n }\r\n\r\n @if(phone && isDropdownOpen){\r\n <div #dropdownRef class=\"input-phone-dropdown\" (click)=\"$event.stopPropagation()\">\r\n <button *ngFor=\"let country of countryOptions\" type=\"button\" class=\"input-phone-dropdown-item\" [ngClass]=\"{'input-phone-dropdown-item--active': selectedCountry.code === country.code}\" (click)=\"selectCountry(country); $event.stopPropagation()\">\r\n {{ country.name }}\r\n </button>\r\n </div>\r\n }\r\n\r\n\r\n\r\n @if(type === 'url'){\r\n <span class=\"input-url-prefix\" [ngClass]=\"{\r\n 'input-url-prefix--default': inputState === 'default',\r\n 'input-url-prefix--focused': inputState === 'focused',\r\n 'input-url-prefix--filled': inputState === 'filled',\r\n 'input-url-prefix--error': inputState === 'error',\r\n 'input-url-prefix--disabled': inputState === 'disabled'\r\n }\">https</span>\r\n }\r\n </div>\r\n\r\n @if(hasError){\r\n @if (errorMessage) {\r\n <p class=\"input-error\">{{ errorMessage }}</p>\r\n }\r\n }\r\n @if(!hasError){\r\n @if(hint){\r\n <p class=\"input-hint\">{{ hint }}</p>\r\n }\r\n }\r\n</div>\r\n", styles: [".input-container{@apply flex flex-col gap-1.5;}.input-label{@apply text-sm font-medium text-[#141414];}.input-label-required{@apply text-[#E7000B] ml-0.5;}.input-wrapper{@apply relative;}.input-field{@apply w-full py-2.5 px-3 text-sm border rounded-[4px] outline-none transition-all duration-200 bg-white;height:40px;box-sizing:border-box}.input-field--default{@apply border-[#E3E3E7] text-[#141414] placeholder:text-[#6B7080];}.input-field--focused{@apply border-[#6B7080] text-[#141414];}.input-field--filled{@apply border-[#E3E3E7] text-[#141414] bg-white;}.input-field--error{@apply border-[#E7000B] text-[#141414];}.input-field--disabled{@apply border-[#E3E3E7] bg-[#F4F4F6] text-[#A1A3AE] cursor-not-allowed;}.input-field--icon{@apply pl-[48px];}.input-field--phone{@apply pl-[80px];}.input-field--url{@apply pl-[72px];}.input-field--icon-left{@apply pl-[48px];}.input-field--icon-right,.input-field--password{@apply pr-[48px];}.input-field--icon.input-field--url{@apply pl-[120px];}.input-field--phone.input-field--icon{@apply pl-[128px];}.input-phone-selector{@apply absolute left-0 top-0 bottom-0 px-3 flex items-center gap-2 cursor-pointer border transition-colors duration-200;border-top-left-radius:4px;border-bottom-left-radius:4px}.input-phone-selector-text{@apply text-xs leading-[18px] text-[#A1A3AE] font-normal;}.input-phone-selector-arrow{@apply w-4 h-4 transition-transform duration-200;}.input-phone-selector-arrow--open{@apply rotate-180;}.input-phone-selector--default{@apply bg-white border-[#E3E3E7];}.input-phone-selector--focused{@apply bg-white border-[#6B7080];}.input-phone-selector--filled{@apply bg-white border-[#E3E3E7];}.input-phone-selector--error{@apply bg-white border-[#E7000B];}.input-phone-selector--disabled{@apply bg-[#F4F4F6] border-[#E3E3E7] cursor-not-allowed;}.input-phone-selector--disabled .input-phone-selector-text{@apply text-[#A1A3AE];}.input-phone-dropdown{@apply absolute left-0 top-full mt-1 w-[80px] bg-white border border-[#E3E3E7] rounded-[4px] shadow-lg z-50 max-h-48 overflow-y-auto;}.input-phone-dropdown-item{@apply w-full px-5 py-2.5 text-center text-xs leading-[18px] text-[#6B7080] hover:bg-[#F9FAFA] transition-colors duration-200 border-none bg-transparent;}.input-phone-dropdown-item--active{@apply bg-[#F9FAFA] text-[#141414];}.input-icon{@apply absolute left-3 top-1/2 -translate-y-1/2 w-6 h-6 pointer-events-none size-6;}.input-wrapper--phone .input-icon{@apply left-[80px];}.input-search-icon{@apply absolute top-1/2 -translate-y-1/2 w-5 h-5;}.input-search-icon--left{@apply left-3;}.input-search-icon--right{@apply right-3;}.input-password-toggle{@apply absolute right-3 top-1/2 -translate-y-1/2 w-5 h-5 p-0 border-0 bg-transparent cursor-pointer outline-none flex items-center justify-center;}.input-password-toggle:disabled{@apply cursor-not-allowed opacity-50;}.input-password-toggle:hover:not(:disabled){@apply opacity-70;}.input-password-icon{@apply w-5 h-5 pointer-events-none;}.input-url-prefix{@apply absolute left-0 top-0 bottom-0 py-2.5 px-3 text-sm text-[#6B7080] bg-white border flex items-center transition-colors duration-200 pointer-events-none;border-top-left-radius:4px;border-bottom-left-radius:4px}.input-wrapper--icon .input-url-prefix{@apply left-[48px];}.input-url-prefix--default{@apply border-[#E3E3E7];}.input-url-prefix--focused{@apply border-[#6B7080];}.input-url-prefix--filled{@apply border-[#E3E3E7];}.input-url-prefix--error{@apply border-[#E7000B];}.input-url-prefix--disabled{@apply bg-[#F4F4F6] text-[#A1A3AE] border-r-[#E3E3E7];}.input-hint{@apply text-xs text-[#868997] font-normal;}.input-error{@apply text-xs text-[#E7000B] font-normal;}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgxMaskDirective, selector: "input[mask], textarea[mask]", inputs: ["mask", "specialCharacters", "patterns", "prefix", "suffix", "thousandSeparator", "decimalMarker", "dropSpecialCharacters", "hiddenInput", "showMaskTyped", "placeHolderCharacter", "shownMaskExpression", "clearIfNotMatch", "validation", "separatorLimit", "allowNegativeNumbers", "leadZeroDateTime", "leadZero", "triggerOnMaskChange", "apm", "inputTransformFn", "outputTransformFn", "keepCharacterPositions", "instantPrefix"], outputs: ["maskFilled"], exportAs: ["mask", "ngxMask"] }, { kind: "ngmodule", type: FormsModule }] });
|
|
3651
|
+
}
|
|
3652
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkInput, decorators: [{
|
|
3653
|
+
type: Component,
|
|
3654
|
+
args: [{ selector: 'bk-input', imports: [CommonModule, NgxMaskDirective, FormsModule], standalone: true, providers: [
|
|
3655
|
+
provideNgxMask(),
|
|
3656
|
+
{
|
|
3657
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3658
|
+
useExisting: forwardRef(() => BkInput),
|
|
3659
|
+
multi: true
|
|
3660
|
+
}
|
|
3661
|
+
], template: "<div class=\"input-container\">\r\n @if(label){\r\n <label [for]=\"id\" class=\"input-label\">\r\n {{ label }}\r\n @if(required){\r\n <span class=\"input-label-required\">*</span>\r\n }\r\n </label>\r\n }\r\n\r\n <div class=\"input-wrapper\" [ngClass]=\"{\r\n 'input-wrapper--url': type === 'url',\r\n 'input-wrapper--phone': phone,\r\n 'input-wrapper--password': password,\r\n 'input-wrapper--icon': iconSrc && showIcon\r\n }\">\r\n\r\n <input\r\n #inputField\r\n [type]=\"currentInputType\"\r\n [id]=\"id\"\r\n [name]=\"name\"\r\n [disabled]=\"disabled\"\r\n [tabindex]=\"tabIndex\"\r\n [readOnly]=\"readOnly\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [autocomplete]=\"autoComplete\"\r\n [autocapitalize]=\"autoCapitalize\"\r\n\r\n [attr.max]=\"max\"\r\n [attr.min]=\"min\"\r\n [attr.step]=\"step\"\r\n\r\n [placeholder]=\"placeHolderText\"\r\n [pattern]=\"pattern\"\r\n [autocomplete]=\"autoComplete\"\r\n [value]=\"inputValue\"\r\n\r\n\r\n\r\n [mask]=\"maskValue\"\r\n [prefix]=\"maskPrefixValue\"\r\n [showMaskTyped]=\"false\"\r\n [dropSpecialCharacters]=\"false\"\r\n (change)=\"handleChange($event)\"\r\n (input)=\"handleInput($event)\"\r\n (focus)=\"handleFocus($event)\"\r\n (blur)=\"handleBlur($event)\"\r\n class=\"input-field\"\r\n\r\n [ngClass]=\"{\r\n 'input-field--url': type === 'url',\r\n 'input-field--phone': phone,\r\n 'input-field--icon-left': iconSrc && showIcon && iconOrientation === 'left',\r\n 'input-field--icon-right': iconSrc && showIcon && iconOrientation === 'right',\r\n 'input-field--password': password,\r\n 'input-field--default': inputState === 'default',\r\n 'input-field--focused': inputState === 'focused',\r\n 'input-field--filled': inputState === 'filled',\r\n 'input-field--error': inputState === 'error',\r\n 'input-field--disabled': inputState === 'disabled'\r\n }\">\r\n\r\n @if(iconSrc && showIcon){\r\n <img (click)=\"handleIconClick($event)\" [src]=\"iconSrc\" [alt]=\"iconAlt\" [ngClass]=\"{\r\n 'input-search-icon--left': iconOrientation === 'left',\r\n 'input-search-icon--right': iconOrientation === 'right',\r\n 'cursor-pointer': !disabled && !readOnly\r\n }\" class=\"input-search-icon\">\r\n }\r\n\r\n @if(showErrorIcon){\r\n <img src=\"../../assets/images/icons/global/info-circle.svg\" class=\"input-search-icon input-search-icon--right\">\r\n }\r\n\r\n @if(password){\r\n <button type=\"button\" (click)=\"togglePasswordVisibility($event)\" class=\"input-password-toggle\" [disabled]=\"disabled\" tabindex=\"-1\">\r\n <img [src]=\"showPassword ? '../../assets/images/icons/global/eye-slash-icon.svg' : '../../assets/images/icons/global/eye-icon.svg'\" [alt]=\"showPassword ? 'Hide password' : 'Show password'\" class=\"input-password-icon\">\r\n </button>\r\n }\r\n\r\n @if(phone){\r\n <div #selectRef class=\"input-phone-selector\" [ngClass]=\"{\r\n 'input-phone-selector--default': inputState === 'default',\r\n 'input-phone-selector--focused': inputState === 'focused',\r\n 'input-phone-selector--filled': inputState === 'filled',\r\n 'input-phone-selector--error': inputState === 'error',\r\n 'input-phone-selector--disabled': inputState === 'disabled'\r\n }\" (click)=\"toggleDropdown($event)\">\r\n <span class=\"input-phone-selector-text\">{{ selectedCountry.name }}</span>\r\n <img src=\"../../assets/images/icons/global/input-arrow-down.svg\" alt=\"Dropdown\" class=\"input-phone-selector-arrow\" [ngClass]=\"{'input-phone-selector-arrow--open': isDropdownOpen}\">\r\n </div>\r\n }\r\n\r\n @if(phone && isDropdownOpen){\r\n <div #dropdownRef class=\"input-phone-dropdown\" (click)=\"$event.stopPropagation()\">\r\n <button *ngFor=\"let country of countryOptions\" type=\"button\" class=\"input-phone-dropdown-item\" [ngClass]=\"{'input-phone-dropdown-item--active': selectedCountry.code === country.code}\" (click)=\"selectCountry(country); $event.stopPropagation()\">\r\n {{ country.name }}\r\n </button>\r\n </div>\r\n }\r\n\r\n\r\n\r\n @if(type === 'url'){\r\n <span class=\"input-url-prefix\" [ngClass]=\"{\r\n 'input-url-prefix--default': inputState === 'default',\r\n 'input-url-prefix--focused': inputState === 'focused',\r\n 'input-url-prefix--filled': inputState === 'filled',\r\n 'input-url-prefix--error': inputState === 'error',\r\n 'input-url-prefix--disabled': inputState === 'disabled'\r\n }\">https</span>\r\n }\r\n </div>\r\n\r\n @if(hasError){\r\n @if (errorMessage) {\r\n <p class=\"input-error\">{{ errorMessage }}</p>\r\n }\r\n }\r\n @if(!hasError){\r\n @if(hint){\r\n <p class=\"input-hint\">{{ hint }}</p>\r\n }\r\n }\r\n</div>\r\n", styles: [".input-container{@apply flex flex-col gap-1.5;}.input-label{@apply text-sm font-medium text-[#141414];}.input-label-required{@apply text-[#E7000B] ml-0.5;}.input-wrapper{@apply relative;}.input-field{@apply w-full py-2.5 px-3 text-sm border rounded-[4px] outline-none transition-all duration-200 bg-white;height:40px;box-sizing:border-box}.input-field--default{@apply border-[#E3E3E7] text-[#141414] placeholder:text-[#6B7080];}.input-field--focused{@apply border-[#6B7080] text-[#141414];}.input-field--filled{@apply border-[#E3E3E7] text-[#141414] bg-white;}.input-field--error{@apply border-[#E7000B] text-[#141414];}.input-field--disabled{@apply border-[#E3E3E7] bg-[#F4F4F6] text-[#A1A3AE] cursor-not-allowed;}.input-field--icon{@apply pl-[48px];}.input-field--phone{@apply pl-[80px];}.input-field--url{@apply pl-[72px];}.input-field--icon-left{@apply pl-[48px];}.input-field--icon-right,.input-field--password{@apply pr-[48px];}.input-field--icon.input-field--url{@apply pl-[120px];}.input-field--phone.input-field--icon{@apply pl-[128px];}.input-phone-selector{@apply absolute left-0 top-0 bottom-0 px-3 flex items-center gap-2 cursor-pointer border transition-colors duration-200;border-top-left-radius:4px;border-bottom-left-radius:4px}.input-phone-selector-text{@apply text-xs leading-[18px] text-[#A1A3AE] font-normal;}.input-phone-selector-arrow{@apply w-4 h-4 transition-transform duration-200;}.input-phone-selector-arrow--open{@apply rotate-180;}.input-phone-selector--default{@apply bg-white border-[#E3E3E7];}.input-phone-selector--focused{@apply bg-white border-[#6B7080];}.input-phone-selector--filled{@apply bg-white border-[#E3E3E7];}.input-phone-selector--error{@apply bg-white border-[#E7000B];}.input-phone-selector--disabled{@apply bg-[#F4F4F6] border-[#E3E3E7] cursor-not-allowed;}.input-phone-selector--disabled .input-phone-selector-text{@apply text-[#A1A3AE];}.input-phone-dropdown{@apply absolute left-0 top-full mt-1 w-[80px] bg-white border border-[#E3E3E7] rounded-[4px] shadow-lg z-50 max-h-48 overflow-y-auto;}.input-phone-dropdown-item{@apply w-full px-5 py-2.5 text-center text-xs leading-[18px] text-[#6B7080] hover:bg-[#F9FAFA] transition-colors duration-200 border-none bg-transparent;}.input-phone-dropdown-item--active{@apply bg-[#F9FAFA] text-[#141414];}.input-icon{@apply absolute left-3 top-1/2 -translate-y-1/2 w-6 h-6 pointer-events-none size-6;}.input-wrapper--phone .input-icon{@apply left-[80px];}.input-search-icon{@apply absolute top-1/2 -translate-y-1/2 w-5 h-5;}.input-search-icon--left{@apply left-3;}.input-search-icon--right{@apply right-3;}.input-password-toggle{@apply absolute right-3 top-1/2 -translate-y-1/2 w-5 h-5 p-0 border-0 bg-transparent cursor-pointer outline-none flex items-center justify-center;}.input-password-toggle:disabled{@apply cursor-not-allowed opacity-50;}.input-password-toggle:hover:not(:disabled){@apply opacity-70;}.input-password-icon{@apply w-5 h-5 pointer-events-none;}.input-url-prefix{@apply absolute left-0 top-0 bottom-0 py-2.5 px-3 text-sm text-[#6B7080] bg-white border flex items-center transition-colors duration-200 pointer-events-none;border-top-left-radius:4px;border-bottom-left-radius:4px}.input-wrapper--icon .input-url-prefix{@apply left-[48px];}.input-url-prefix--default{@apply border-[#E3E3E7];}.input-url-prefix--focused{@apply border-[#6B7080];}.input-url-prefix--filled{@apply border-[#E3E3E7];}.input-url-prefix--error{@apply border-[#E7000B];}.input-url-prefix--disabled{@apply bg-[#F4F4F6] text-[#A1A3AE] border-r-[#E3E3E7];}.input-hint{@apply text-xs text-[#868997] font-normal;}.input-error{@apply text-xs text-[#E7000B] font-normal;}\n"] }]
|
|
3662
|
+
}], propDecorators: { id: [{
|
|
3663
|
+
type: Input
|
|
3664
|
+
}], name: [{
|
|
3665
|
+
type: Input
|
|
3666
|
+
}], mask: [{
|
|
3667
|
+
type: Input
|
|
3668
|
+
}], autoComplete: [{
|
|
3669
|
+
type: Input
|
|
3670
|
+
}], label: [{
|
|
3671
|
+
type: Input
|
|
3672
|
+
}], placeholder: [{
|
|
3673
|
+
type: Input
|
|
3674
|
+
}], hint: [{
|
|
3675
|
+
type: Input
|
|
3676
|
+
}], required: [{
|
|
3677
|
+
type: Input
|
|
3678
|
+
}], type: [{
|
|
3679
|
+
type: Input
|
|
3680
|
+
}], value: [{
|
|
3681
|
+
type: Input
|
|
3682
|
+
}], hasError: [{
|
|
3683
|
+
type: Input
|
|
3684
|
+
}], showErrorIcon: [{
|
|
3685
|
+
type: Input
|
|
3686
|
+
}], errorMessage: [{
|
|
3687
|
+
type: Input
|
|
3688
|
+
}], disabled: [{
|
|
3689
|
+
type: Input
|
|
3690
|
+
}], tabIndex: [{
|
|
3691
|
+
type: Input
|
|
3692
|
+
}], readOnly: [{
|
|
3693
|
+
type: Input
|
|
3694
|
+
}], autoCapitalize: [{
|
|
3695
|
+
type: Input
|
|
3696
|
+
}], inputMode: [{
|
|
3697
|
+
type: Input
|
|
3698
|
+
}], iconSrc: [{
|
|
3699
|
+
type: Input
|
|
3700
|
+
}], iconAlt: [{
|
|
3701
|
+
type: Input
|
|
3702
|
+
}], showIcon: [{
|
|
3703
|
+
type: Input
|
|
3704
|
+
}], phone: [{
|
|
3705
|
+
type: Input
|
|
3706
|
+
}], countryCode: [{
|
|
3707
|
+
type: Input
|
|
3708
|
+
}], countryOptions: [{
|
|
3709
|
+
type: Input
|
|
3710
|
+
}], iconOrientation: [{
|
|
3711
|
+
type: Input
|
|
3712
|
+
}], password: [{
|
|
3713
|
+
type: Input
|
|
3714
|
+
}], showPassword: [{
|
|
3715
|
+
type: Input
|
|
3716
|
+
}], pattern: [{
|
|
3717
|
+
type: Input
|
|
3718
|
+
}], max: [{
|
|
3719
|
+
type: Input
|
|
3720
|
+
}], min: [{
|
|
3721
|
+
type: Input
|
|
3722
|
+
}], step: [{
|
|
3723
|
+
type: Input
|
|
3724
|
+
}], maxlength: [{
|
|
3725
|
+
type: Input
|
|
3726
|
+
}], minlength: [{
|
|
3727
|
+
type: Input
|
|
3728
|
+
}], dropdownRef: [{
|
|
3729
|
+
type: ViewChild,
|
|
3730
|
+
args: ['dropdownRef']
|
|
3731
|
+
}], selectRef: [{
|
|
3732
|
+
type: ViewChild,
|
|
3733
|
+
args: ['selectRef']
|
|
3734
|
+
}], inputField: [{
|
|
3735
|
+
type: ViewChild,
|
|
3736
|
+
args: ['inputField']
|
|
3737
|
+
}], input: [{
|
|
3738
|
+
type: Output
|
|
3739
|
+
}], change: [{
|
|
3740
|
+
type: Output
|
|
3741
|
+
}], focus: [{
|
|
3742
|
+
type: Output
|
|
3743
|
+
}], blur: [{
|
|
3744
|
+
type: Output
|
|
3745
|
+
}], clicked: [{
|
|
3746
|
+
type: Output
|
|
3747
|
+
}], onDocumentClick: [{
|
|
3748
|
+
type: HostListener,
|
|
3749
|
+
args: ['document:click', ['$event']]
|
|
3750
|
+
}] } });
|
|
3751
|
+
|
|
3752
|
+
class BkChips {
|
|
3753
|
+
badgeInput;
|
|
3754
|
+
fieldWrapper;
|
|
3755
|
+
// --- Configuration Inputs ---
|
|
3756
|
+
id;
|
|
3757
|
+
name;
|
|
3758
|
+
label = '';
|
|
3759
|
+
placeholder = '';
|
|
3760
|
+
hint = '';
|
|
3761
|
+
required = false;
|
|
3762
|
+
disabled = false;
|
|
3763
|
+
readOnly = false;
|
|
3764
|
+
/**
|
|
3765
|
+
* If true, displays the component in an error state (red border).
|
|
3766
|
+
* It also replaces the hint text with the error message.
|
|
3767
|
+
*/
|
|
3768
|
+
hasError = false;
|
|
3769
|
+
errorMessage = 'This is a error message';
|
|
3770
|
+
// =================== Output Emitter ===================
|
|
3771
|
+
input = new EventEmitter();
|
|
3772
|
+
change = new EventEmitter();
|
|
3773
|
+
focus = new EventEmitter();
|
|
3774
|
+
blur = new EventEmitter();
|
|
3775
|
+
// --- State Properties ---
|
|
3776
|
+
badges = [];
|
|
3777
|
+
inputValue = '';
|
|
3778
|
+
isFocused = false;
|
|
3779
|
+
needsScroll = false;
|
|
3780
|
+
// --- ControlValueAccessor Methods ---
|
|
3781
|
+
onChange = () => { };
|
|
3782
|
+
onTouched = () => { };
|
|
3783
|
+
// Get input state for styling variants
|
|
3784
|
+
get inputState() {
|
|
3785
|
+
if (this.disabled)
|
|
3786
|
+
return 'disabled';
|
|
3787
|
+
if (this.hasError)
|
|
3788
|
+
return 'error';
|
|
3789
|
+
if (this.isFocused)
|
|
3790
|
+
return 'focused';
|
|
3791
|
+
if (this.badges.length > 0 || this.inputValue.length > 0)
|
|
3792
|
+
return 'filled';
|
|
3793
|
+
return 'default';
|
|
3794
|
+
}
|
|
3795
|
+
// Handle keydown events (Enter to add badge, Backspace to remove)
|
|
3796
|
+
onKeyDown(event) {
|
|
3797
|
+
if (this.disabled)
|
|
3798
|
+
return;
|
|
3799
|
+
// Add badge on Enter or comma
|
|
3800
|
+
if (event.key === 'Enter' || event.key === ',') {
|
|
3801
|
+
event.preventDefault();
|
|
3802
|
+
this.addBadge();
|
|
3803
|
+
}
|
|
3804
|
+
// Remove last badge on Backspace when input is empty
|
|
3805
|
+
else if (event.key === 'Backspace' && this.inputValue === '' && this.badges.length > 0) {
|
|
3806
|
+
this.removeBadge(this.badges.length - 1);
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
// Add a badge from the current input value
|
|
3810
|
+
addBadge() {
|
|
3811
|
+
const trimmedValue = this.inputValue.trim();
|
|
3812
|
+
if (trimmedValue && !this.badges.includes(trimmedValue)) {
|
|
3813
|
+
this.badges.push(trimmedValue);
|
|
3814
|
+
this.inputValue = '';
|
|
3815
|
+
this.updateValue();
|
|
3816
|
+
// Check if scroll is needed after adding badge (with delay for DOM update)
|
|
3817
|
+
setTimeout(() => this.checkScrollNeeded(), 10);
|
|
3818
|
+
}
|
|
3819
|
+
else if (trimmedValue && this.badges.includes(trimmedValue)) {
|
|
3820
|
+
// Optionally show a message that badge already exists
|
|
3821
|
+
this.inputValue = '';
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
// Remove a badge at the given index
|
|
3825
|
+
removeBadge(index) {
|
|
3826
|
+
if (this.disabled)
|
|
3827
|
+
return;
|
|
3828
|
+
this.badges.splice(index, 1);
|
|
3829
|
+
this.updateValue();
|
|
3830
|
+
// Check if scroll is needed after removing badge (with delay for DOM update)
|
|
3831
|
+
setTimeout(() => {
|
|
3832
|
+
this.checkScrollNeeded();
|
|
3833
|
+
if (this.badgeInput) {
|
|
3834
|
+
this.badgeInput.nativeElement.focus();
|
|
3835
|
+
}
|
|
3836
|
+
}, 10);
|
|
3837
|
+
}
|
|
3838
|
+
// Check if scrolling is needed (when content wraps)
|
|
3839
|
+
checkScrollNeeded() {
|
|
3840
|
+
if (this.fieldWrapper && this.fieldWrapper.nativeElement) {
|
|
3841
|
+
const wrapper = this.fieldWrapper.nativeElement;
|
|
3842
|
+
// Get all badge items
|
|
3843
|
+
const badgeItems = wrapper.querySelectorAll('.input-badge-item');
|
|
3844
|
+
if (badgeItems.length === 0) {
|
|
3845
|
+
// No badges, no scroll needed
|
|
3846
|
+
this.needsScroll = false;
|
|
3847
|
+
return;
|
|
3848
|
+
}
|
|
3849
|
+
// Get the first badge's top position
|
|
3850
|
+
const firstBadge = badgeItems[0];
|
|
3851
|
+
const firstBadgeTop = firstBadge.offsetTop;
|
|
3852
|
+
// Check if any badge is on a different line (different top position)
|
|
3853
|
+
let hasWrapped = false;
|
|
3854
|
+
for (let i = 1; i < badgeItems.length; i++) {
|
|
3855
|
+
const badge = badgeItems[i];
|
|
3856
|
+
// If a badge's top position is different, it means it wrapped to a new line
|
|
3857
|
+
if (Math.abs(badge.offsetTop - firstBadgeTop) > 5) {
|
|
3858
|
+
hasWrapped = true;
|
|
3859
|
+
break;
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
// Also check if the input field is on a different line
|
|
3863
|
+
if (!hasWrapped && this.badgeInput && this.badgeInput.nativeElement) {
|
|
3864
|
+
const inputTop = this.badgeInput.nativeElement.offsetTop;
|
|
3865
|
+
if (Math.abs(inputTop - firstBadgeTop) > 5) {
|
|
3866
|
+
hasWrapped = true;
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
// Only enable scroll if content actually wrapped
|
|
3870
|
+
this.needsScroll = hasWrapped;
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
// Update the value and notify Angular Forms
|
|
3874
|
+
updateValue() {
|
|
3875
|
+
this.onChange([...this.badges]);
|
|
3876
|
+
this.change.emit([...this.badges]);
|
|
3877
|
+
}
|
|
3878
|
+
// Focus the input field
|
|
3879
|
+
focusInput() {
|
|
3880
|
+
if (!this.disabled && this.badgeInput) {
|
|
3881
|
+
this.badgeInput.nativeElement.focus();
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
ngAfterViewInit() {
|
|
3885
|
+
// Check scroll after view initializes
|
|
3886
|
+
setTimeout(() => this.checkScrollNeeded(), 10);
|
|
3887
|
+
}
|
|
3888
|
+
// Called when Angular writes a value TO the component (e.g. initial value)
|
|
3889
|
+
writeValue(value) {
|
|
3890
|
+
if (Array.isArray(value)) {
|
|
3891
|
+
this.badges = [...value];
|
|
3892
|
+
}
|
|
3893
|
+
else {
|
|
3894
|
+
this.badges = [];
|
|
3895
|
+
}
|
|
3896
|
+
this.inputValue = '';
|
|
3897
|
+
// Check scroll after value is written (with delay for DOM update)
|
|
3898
|
+
setTimeout(() => this.checkScrollNeeded(), 10);
|
|
3899
|
+
}
|
|
3900
|
+
// Register function to call when value changes
|
|
3901
|
+
registerOnChange(fn) {
|
|
3902
|
+
this.onChange = fn;
|
|
3903
|
+
}
|
|
3904
|
+
// Register function to call when component is touched/blurred
|
|
3905
|
+
registerOnTouched(fn) {
|
|
3906
|
+
this.onTouched = fn;
|
|
3907
|
+
}
|
|
3908
|
+
// Called when the component is disabled via the form control
|
|
3909
|
+
setDisabledState(disabled) {
|
|
3910
|
+
this.disabled = disabled;
|
|
3911
|
+
}
|
|
3912
|
+
// =================== Event Handlers ===================
|
|
3913
|
+
// Called when the value in the UI changes (user types)
|
|
3914
|
+
handleInput(event) {
|
|
3915
|
+
const input = event.target;
|
|
3916
|
+
this.inputValue = input.value;
|
|
3917
|
+
}
|
|
3918
|
+
handleFocus(event) {
|
|
3919
|
+
if (!this.disabled) {
|
|
3920
|
+
this.isFocused = true;
|
|
3921
|
+
}
|
|
3922
|
+
this.onChange([...this.badges]);
|
|
3923
|
+
this.focus.emit(event);
|
|
3924
|
+
}
|
|
3925
|
+
handleBlur(event) {
|
|
3926
|
+
this.isFocused = false;
|
|
3927
|
+
this.onTouched();
|
|
3928
|
+
this.onChange([...this.badges]);
|
|
3929
|
+
this.blur.emit(event);
|
|
3930
|
+
}
|
|
3931
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkChips, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3932
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkChips, isStandalone: true, selector: "bk-chips", inputs: { id: "id", name: "name", label: "label", placeholder: "placeholder", hint: "hint", required: "required", disabled: "disabled", readOnly: "readOnly", hasError: "hasError", errorMessage: "errorMessage" }, outputs: { input: "input", change: "change", focus: "focus", blur: "blur" }, providers: [
|
|
3933
|
+
{
|
|
3934
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3935
|
+
useExisting: forwardRef(() => BkChips),
|
|
3936
|
+
multi: true
|
|
3937
|
+
}
|
|
3938
|
+
], viewQueries: [{ propertyName: "badgeInput", first: true, predicate: ["badgeInput"], descendants: true }, { propertyName: "fieldWrapper", first: true, predicate: ["fieldWrapper"], descendants: true }], ngImport: i0, template: "<div class=\"input-badge-container\">\r\n @if (label) {\r\n <label\r\n [for]=\"id\"\r\n class=\"input-badge-label\">\r\n {{ label }}\r\n @if (required) {\r\n <span class=\"input-badge-label-required\">*</span>\r\n }\r\n </label>\r\n }\r\n\r\n <div class=\"input-badge-wrapper\">\r\n <div\r\n #fieldWrapper\r\n class=\"input-badge-field-wrapper\"\r\n [ngClass]=\"{\r\n 'input-badge-field-wrapper--default': inputState === 'default',\r\n 'input-badge-field-wrapper--focused': inputState === 'focused',\r\n 'input-badge-field-wrapper--filled': inputState === 'filled',\r\n 'input-badge-field-wrapper--error': inputState === 'error',\r\n 'input-badge-field-wrapper--disabled': inputState === 'disabled',\r\n 'input-badge-field-wrapper--scrollable': needsScroll\r\n }\"\r\n (click)=\"focusInput()\"\r\n >\r\n <!-- Badges -->\r\n @for (badge of badges; track badge; let i = $index) {\r\n <div class=\"input-badge-item\">\r\n <span class=\"input-badge-item-text\">{{ badge }}</span>\r\n @if (!disabled) {\r\n <button type=\"button\" (click)=\"removeBadge(i); $event.stopPropagation()\"class=\"input-badge-item-close\" >\r\n <img src=\"../../../../assets/images/icons/global/badge-close.svg\" alt=\"Remove\" class=\"input-badge-item-close-icon\"/>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Input Field -->\r\n <input\r\n #badgeInput\r\n type=\"text\"\r\n [id]=\"id\"\r\n [name]=\"name\"\r\n [value]=\"inputValue\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (input)=\"handleInput($event)\"\r\n (focus)=\"handleFocus($event)\"\r\n (blur)=\"handleBlur($event)\"\r\n [placeholder]=\"badges.length === 0 ? placeholder : ''\"\r\n [disabled]=\"disabled\"\r\n [readOnly]=\"readOnly\"\r\n class=\"input-badge-input\"\r\n [ngClass]=\"{\r\n 'input-badge-input--default': inputState === 'default',\r\n 'input-badge-input--focused': inputState === 'focused',\r\n 'input-badge-input--filled': inputState === 'filled',\r\n 'input-badge-input--error': inputState === 'error',\r\n 'input-badge-input--disabled': inputState === 'disabled'\r\n }\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"input-badge-footer\">\r\n <div class=\"input-badge-footer-content\">\r\n @if (hasError) {\r\n <span class=\"input-badge-error\"> {{ errorMessage }}</span>\r\n } @else if (hint) {\r\n <span class=\"input-badge-hint\"> {{ hint }}</span>\r\n }\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".input-badge-container{@apply flex flex-col gap-1.5 w-full;}.input-badge-label{@apply text-sm font-medium text-[#141414] block;}.input-badge-label-required{@apply text-[#E7000B] ml-0.5;}.input-badge-wrapper{@apply relative;}.input-badge-field-wrapper{@apply w-full px-3 py-2 text-sm border rounded-[4px] outline-none transition-colors duration-200 bg-white flex flex-wrap gap-2 items-start;height:40px;box-sizing:border-box;overflow-y:hidden;overflow-x:hidden}.input-badge-field-wrapper--scrollable{overflow-y:auto}.input-badge-field-wrapper--default{@apply border-[#E3E3E7] text-[#141414];}.input-badge-field-wrapper--default:focus-within{@apply border-[#6B7080];}.input-badge-field-wrapper--focused{@apply border-[#6B7080] text-[#141414];}.input-badge-field-wrapper--filled{@apply border-[#E3E3E7] text-[#141414] bg-white;}.input-badge-field-wrapper--filled:focus-within{@apply border-[#6B7080];}.input-badge-field-wrapper--error{@apply border-[#FA727A] text-[#141414];}.input-badge-field-wrapper--disabled{@apply bg-[#F4F4F6] text-[#A1A3AE] border-[#E3E3E7] cursor-not-allowed;}.input-badge-item{@apply inline-flex items-center gap-1 px-2 py-[2.5px] bg-white border border-[#E3E3E7] rounded-[4px];}.input-badge-item-text{@apply text-sm leading-[18px] font-normal text-[#6B7080];}.input-badge-item-close{@apply cursor-pointer outline-none w-3 h-3;}.input-badge-input{@apply flex-1 min-w-[120px] outline-none bg-transparent text-[#141414] placeholder:text-[#6B7080] h-auto;flex-basis:120px}.input-badge-input--default,.input-badge-input--focused,.input-badge-input--filled,.input-badge-input--error{@apply text-[#141414];}.input-badge-input--disabled{@apply text-[#A1A3AE] cursor-not-allowed;}.input-badge-footer{@apply flex justify-between items-start font-normal text-sm;}.input-badge-footer-content{@apply flex-1;}.input-badge-hint{@apply text-xs text-[#868997] font-normal;}.input-badge-error{@apply text-xs text-[#F34050] font-normal;}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }] });
|
|
3939
|
+
}
|
|
3940
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkChips, decorators: [{
|
|
3941
|
+
type: Component,
|
|
3942
|
+
args: [{ selector: 'bk-chips', standalone: true, imports: [CommonModule, FormsModule], providers: [
|
|
3943
|
+
{
|
|
3944
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3945
|
+
useExisting: forwardRef(() => BkChips),
|
|
3946
|
+
multi: true
|
|
3947
|
+
}
|
|
3948
|
+
], template: "<div class=\"input-badge-container\">\r\n @if (label) {\r\n <label\r\n [for]=\"id\"\r\n class=\"input-badge-label\">\r\n {{ label }}\r\n @if (required) {\r\n <span class=\"input-badge-label-required\">*</span>\r\n }\r\n </label>\r\n }\r\n\r\n <div class=\"input-badge-wrapper\">\r\n <div\r\n #fieldWrapper\r\n class=\"input-badge-field-wrapper\"\r\n [ngClass]=\"{\r\n 'input-badge-field-wrapper--default': inputState === 'default',\r\n 'input-badge-field-wrapper--focused': inputState === 'focused',\r\n 'input-badge-field-wrapper--filled': inputState === 'filled',\r\n 'input-badge-field-wrapper--error': inputState === 'error',\r\n 'input-badge-field-wrapper--disabled': inputState === 'disabled',\r\n 'input-badge-field-wrapper--scrollable': needsScroll\r\n }\"\r\n (click)=\"focusInput()\"\r\n >\r\n <!-- Badges -->\r\n @for (badge of badges; track badge; let i = $index) {\r\n <div class=\"input-badge-item\">\r\n <span class=\"input-badge-item-text\">{{ badge }}</span>\r\n @if (!disabled) {\r\n <button type=\"button\" (click)=\"removeBadge(i); $event.stopPropagation()\"class=\"input-badge-item-close\" >\r\n <img src=\"../../../../assets/images/icons/global/badge-close.svg\" alt=\"Remove\" class=\"input-badge-item-close-icon\"/>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Input Field -->\r\n <input\r\n #badgeInput\r\n type=\"text\"\r\n [id]=\"id\"\r\n [name]=\"name\"\r\n [value]=\"inputValue\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (input)=\"handleInput($event)\"\r\n (focus)=\"handleFocus($event)\"\r\n (blur)=\"handleBlur($event)\"\r\n [placeholder]=\"badges.length === 0 ? placeholder : ''\"\r\n [disabled]=\"disabled\"\r\n [readOnly]=\"readOnly\"\r\n class=\"input-badge-input\"\r\n [ngClass]=\"{\r\n 'input-badge-input--default': inputState === 'default',\r\n 'input-badge-input--focused': inputState === 'focused',\r\n 'input-badge-input--filled': inputState === 'filled',\r\n 'input-badge-input--error': inputState === 'error',\r\n 'input-badge-input--disabled': inputState === 'disabled'\r\n }\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"input-badge-footer\">\r\n <div class=\"input-badge-footer-content\">\r\n @if (hasError) {\r\n <span class=\"input-badge-error\"> {{ errorMessage }}</span>\r\n } @else if (hint) {\r\n <span class=\"input-badge-hint\"> {{ hint }}</span>\r\n }\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".input-badge-container{@apply flex flex-col gap-1.5 w-full;}.input-badge-label{@apply text-sm font-medium text-[#141414] block;}.input-badge-label-required{@apply text-[#E7000B] ml-0.5;}.input-badge-wrapper{@apply relative;}.input-badge-field-wrapper{@apply w-full px-3 py-2 text-sm border rounded-[4px] outline-none transition-colors duration-200 bg-white flex flex-wrap gap-2 items-start;height:40px;box-sizing:border-box;overflow-y:hidden;overflow-x:hidden}.input-badge-field-wrapper--scrollable{overflow-y:auto}.input-badge-field-wrapper--default{@apply border-[#E3E3E7] text-[#141414];}.input-badge-field-wrapper--default:focus-within{@apply border-[#6B7080];}.input-badge-field-wrapper--focused{@apply border-[#6B7080] text-[#141414];}.input-badge-field-wrapper--filled{@apply border-[#E3E3E7] text-[#141414] bg-white;}.input-badge-field-wrapper--filled:focus-within{@apply border-[#6B7080];}.input-badge-field-wrapper--error{@apply border-[#FA727A] text-[#141414];}.input-badge-field-wrapper--disabled{@apply bg-[#F4F4F6] text-[#A1A3AE] border-[#E3E3E7] cursor-not-allowed;}.input-badge-item{@apply inline-flex items-center gap-1 px-2 py-[2.5px] bg-white border border-[#E3E3E7] rounded-[4px];}.input-badge-item-text{@apply text-sm leading-[18px] font-normal text-[#6B7080];}.input-badge-item-close{@apply cursor-pointer outline-none w-3 h-3;}.input-badge-input{@apply flex-1 min-w-[120px] outline-none bg-transparent text-[#141414] placeholder:text-[#6B7080] h-auto;flex-basis:120px}.input-badge-input--default,.input-badge-input--focused,.input-badge-input--filled,.input-badge-input--error{@apply text-[#141414];}.input-badge-input--disabled{@apply text-[#A1A3AE] cursor-not-allowed;}.input-badge-footer{@apply flex justify-between items-start font-normal text-sm;}.input-badge-footer-content{@apply flex-1;}.input-badge-hint{@apply text-xs text-[#868997] font-normal;}.input-badge-error{@apply text-xs text-[#F34050] font-normal;}\n"] }]
|
|
3949
|
+
}], propDecorators: { badgeInput: [{
|
|
3950
|
+
type: ViewChild,
|
|
3951
|
+
args: ['badgeInput']
|
|
3952
|
+
}], fieldWrapper: [{
|
|
3953
|
+
type: ViewChild,
|
|
3954
|
+
args: ['fieldWrapper']
|
|
3955
|
+
}], id: [{
|
|
3956
|
+
type: Input
|
|
3957
|
+
}], name: [{
|
|
3958
|
+
type: Input
|
|
3959
|
+
}], label: [{
|
|
3960
|
+
type: Input
|
|
3961
|
+
}], placeholder: [{
|
|
3962
|
+
type: Input
|
|
3963
|
+
}], hint: [{
|
|
3964
|
+
type: Input
|
|
3965
|
+
}], required: [{
|
|
3966
|
+
type: Input
|
|
3967
|
+
}], disabled: [{
|
|
3968
|
+
type: Input
|
|
3969
|
+
}], readOnly: [{
|
|
3970
|
+
type: Input
|
|
3971
|
+
}], hasError: [{
|
|
3972
|
+
type: Input
|
|
3973
|
+
}], errorMessage: [{
|
|
3974
|
+
type: Input
|
|
3975
|
+
}], input: [{
|
|
3976
|
+
type: Output
|
|
3977
|
+
}], change: [{
|
|
3978
|
+
type: Output
|
|
3979
|
+
}], focus: [{
|
|
3980
|
+
type: Output
|
|
3981
|
+
}], blur: [{
|
|
3982
|
+
type: Output
|
|
3983
|
+
}] } });
|
|
3984
|
+
|
|
3985
|
+
class BkTabs {
|
|
3986
|
+
list = [];
|
|
3987
|
+
activeTabId = '';
|
|
3988
|
+
disabled = false;
|
|
3989
|
+
change = new EventEmitter();
|
|
3990
|
+
// Set active tab and emit change event
|
|
3991
|
+
setActiveTab(tab) {
|
|
3992
|
+
debugger;
|
|
3993
|
+
if (tab?.disabled || this.disabled)
|
|
3994
|
+
return;
|
|
3995
|
+
this.activeTabId = tab.id;
|
|
3996
|
+
this.change.emit(tab);
|
|
3997
|
+
}
|
|
3998
|
+
// Check if a tab is active
|
|
3999
|
+
isActive(tabId) {
|
|
4000
|
+
return this.activeTabId === tabId;
|
|
4001
|
+
}
|
|
4002
|
+
// Get the appropriate icon for a tab based on its active state
|
|
4003
|
+
getTabIcon(tab) {
|
|
4004
|
+
if (this.isActive(tab.id) && tab.iconActive) {
|
|
4005
|
+
return tab.iconActive;
|
|
4006
|
+
}
|
|
4007
|
+
return tab.icon;
|
|
4008
|
+
}
|
|
4009
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4010
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkTabs, isStandalone: true, selector: "bk-tabs", inputs: { list: "list", activeTabId: "activeTabId", disabled: "disabled" }, outputs: { change: "change" }, ngImport: i0, template: "<div class=\"tabs-container\">\r\n <ul class=\"tabs-list\" role=\"tablist\">\r\n @for (tab of list; track tab.id; let i = $index) {\r\n <li class=\"tabs-item\" role=\"presentation\">\r\n <button\r\n type=\"button\"\r\n [id]=\"'tab-' + tab.id\"\r\n [attr.aria-selected]=\"isActive(tab.id)\"\r\n [attr.aria-controls]=\"'panel-' + tab.id\"\r\n [disabled]=\"tab.disabled\"\r\n [class.tabs-button--active]=\"isActive(tab.id)\"\r\n [class.tabs-button--disabled]=\"disabled || tab.disabled\"\r\n [class.tabs-button--no-icon]=\"!getTabIcon(tab)\"\r\n class=\"tabs-button\"\r\n (click)=\"setActiveTab(tab)\"\r\n role=\"tab\">\r\n @if (getTabIcon(tab)) {\r\n <img\r\n [src]=\"getTabIcon(tab)\"\r\n [alt]=\"tab.iconAlt || tab.label\"\r\n class=\"tabs-icon\">\r\n }\r\n <span class=\"tabs-label\">{{ tab.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n</div>\r\n", styles: [".tabs-container{@apply w-full;}.tabs-list{@apply flex gap-2 list-none m-0 p-0;}.tabs-item{@apply flex-shrink-0;}.tabs-button{@apply flex items-center gap-[6px] px-3 py-2 rounded-md border-0 bg-transparent cursor-pointer transition-all duration-200 outline-none;@apply text-sm leading-[11px] font-medium text-[#141414] tracking-[-.28];}.tabs-button--no-icon{@apply gap-0 px-3 py-[10.5px];}.tabs-button--active{@apply bg-[#141414] text-white;}.tabs-button--disabled{@apply opacity-50 cursor-not-allowed;}.tabs-icon{@apply size-4 flex-shrink-0;transition:opacity .2s ease}.tabs-label{@apply whitespace-nowrap;}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
4011
|
+
}
|
|
4012
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkTabs, decorators: [{
|
|
4013
|
+
type: Component,
|
|
4014
|
+
args: [{ selector: 'bk-tabs', standalone: true, imports: [CommonModule], template: "<div class=\"tabs-container\">\r\n <ul class=\"tabs-list\" role=\"tablist\">\r\n @for (tab of list; track tab.id; let i = $index) {\r\n <li class=\"tabs-item\" role=\"presentation\">\r\n <button\r\n type=\"button\"\r\n [id]=\"'tab-' + tab.id\"\r\n [attr.aria-selected]=\"isActive(tab.id)\"\r\n [attr.aria-controls]=\"'panel-' + tab.id\"\r\n [disabled]=\"tab.disabled\"\r\n [class.tabs-button--active]=\"isActive(tab.id)\"\r\n [class.tabs-button--disabled]=\"disabled || tab.disabled\"\r\n [class.tabs-button--no-icon]=\"!getTabIcon(tab)\"\r\n class=\"tabs-button\"\r\n (click)=\"setActiveTab(tab)\"\r\n role=\"tab\">\r\n @if (getTabIcon(tab)) {\r\n <img\r\n [src]=\"getTabIcon(tab)\"\r\n [alt]=\"tab.iconAlt || tab.label\"\r\n class=\"tabs-icon\">\r\n }\r\n <span class=\"tabs-label\">{{ tab.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n</div>\r\n", styles: [".tabs-container{@apply w-full;}.tabs-list{@apply flex gap-2 list-none m-0 p-0;}.tabs-item{@apply flex-shrink-0;}.tabs-button{@apply flex items-center gap-[6px] px-3 py-2 rounded-md border-0 bg-transparent cursor-pointer transition-all duration-200 outline-none;@apply text-sm leading-[11px] font-medium text-[#141414] tracking-[-.28];}.tabs-button--no-icon{@apply gap-0 px-3 py-[10.5px];}.tabs-button--active{@apply bg-[#141414] text-white;}.tabs-button--disabled{@apply opacity-50 cursor-not-allowed;}.tabs-icon{@apply size-4 flex-shrink-0;transition:opacity .2s ease}.tabs-label{@apply whitespace-nowrap;}\n"] }]
|
|
4015
|
+
}], propDecorators: { list: [{
|
|
4016
|
+
type: Input
|
|
4017
|
+
}], activeTabId: [{
|
|
4018
|
+
type: Input
|
|
4019
|
+
}], disabled: [{
|
|
4020
|
+
type: Input
|
|
4021
|
+
}], change: [{
|
|
4022
|
+
type: Output
|
|
4023
|
+
}] } });
|
|
4024
|
+
|
|
4025
|
+
/*
|
|
4026
|
+
* Public API Surface of brickclay-lib
|
|
4027
|
+
*/
|
|
4028
|
+
//Icons
|
|
4029
|
+
|
|
4030
|
+
/**
|
|
4031
|
+
* Generated bundle index. Do not edit.
|
|
4032
|
+
*/
|
|
4033
|
+
|
|
4034
|
+
export { BkBadge, BkButton, BkButtonGroup, BkCheckbox, BkChips, BkCustomCalendar, BkGrid, BkIconButton, BkInput, BkPill, BkRadioButton, BkScheduledDatePicker, BkSelect, BkSpinner, BkTabs, BkTextarea, BkTimePicker, BkToggle, BrickclayIcons, BrickclayLib, CalendarManagerService, CalendarModule };
|
|
4035
|
+
//# sourceMappingURL=brickclay-org-ui.mjs.map
|