@framesquared/form 0.1.0

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/dist/index.js ADDED
@@ -0,0 +1,2256 @@
1
+ // src/field/Field.ts
2
+ import { Component } from "@framesquared/component";
3
+ var Field = class extends Component {
4
+ static $className = "Ext.form.field.Field";
5
+ constructor(config = {}) {
6
+ super({ xtype: "field", ...config });
7
+ }
8
+ initialize() {
9
+ super.initialize();
10
+ const cfg = this._config;
11
+ this._name = cfg.name ?? "";
12
+ this._originalValue = cfg.value ?? "";
13
+ this._value = cfg.value ?? "";
14
+ this._errors = [];
15
+ this._wasValid = true;
16
+ this._wasDirty = false;
17
+ this._labelEl = null;
18
+ this._bodyWrapEl = null;
19
+ this._errorEl = null;
20
+ }
21
+ afterRender() {
22
+ super.afterRender();
23
+ const cfg = this._config;
24
+ const el = this.el;
25
+ el.classList.add("x-field");
26
+ const labelAlign = cfg.labelAlign ?? "left";
27
+ el.classList.add(`x-label-${labelAlign}`);
28
+ if (cfg.fieldLabel) {
29
+ this._labelEl = document.createElement("label");
30
+ this._labelEl.classList.add("x-field-label");
31
+ this._labelEl.textContent = cfg.fieldLabel + (cfg.labelSeparator ?? ":");
32
+ if (cfg.labelWidth) this._labelEl.style.width = `${cfg.labelWidth}px`;
33
+ if (cfg.hideLabel) this._labelEl.style.display = "none";
34
+ el.appendChild(this._labelEl);
35
+ }
36
+ this._bodyWrapEl = document.createElement("div");
37
+ this._bodyWrapEl.classList.add("x-field-body");
38
+ el.appendChild(this._bodyWrapEl);
39
+ }
40
+ /**
41
+ * Called after the full subclass afterRender chain completes.
42
+ * Subclasses that need post-render init should call super.afterRender()
43
+ * then do their work — this method sets initial validity once
44
+ * the input element exists.
45
+ */
46
+ initFieldValidity() {
47
+ this._wasValid = this.isValid();
48
+ }
49
+ // -----------------------------------------------------------------------
50
+ // Value
51
+ // -----------------------------------------------------------------------
52
+ getValue() {
53
+ return this._value;
54
+ }
55
+ setValue(value) {
56
+ const oldValue = this._value;
57
+ this._value = value;
58
+ this.onValueChange(oldValue, value);
59
+ }
60
+ getRawValue() {
61
+ return String(this._value ?? "");
62
+ }
63
+ setRawValue(value) {
64
+ this.setValue(value);
65
+ }
66
+ onValueChange(oldValue, newValue) {
67
+ if (oldValue !== newValue) {
68
+ this.fire("change", this, newValue, oldValue);
69
+ this.checkDirty();
70
+ this.checkValidity();
71
+ }
72
+ }
73
+ // -----------------------------------------------------------------------
74
+ // Dirty tracking
75
+ // -----------------------------------------------------------------------
76
+ isDirty() {
77
+ return this._value !== this._originalValue;
78
+ }
79
+ reset() {
80
+ this.setValue(this._originalValue);
81
+ this.clearInvalid();
82
+ }
83
+ checkDirty() {
84
+ const dirty = this.isDirty();
85
+ if (dirty !== this._wasDirty) {
86
+ this._wasDirty = dirty;
87
+ this.fire("dirtychange", this, dirty);
88
+ }
89
+ }
90
+ // -----------------------------------------------------------------------
91
+ // Validation
92
+ // -----------------------------------------------------------------------
93
+ getErrors() {
94
+ return this.computeErrors();
95
+ }
96
+ isValid() {
97
+ const errors = this.computeErrors();
98
+ return errors.length === 0;
99
+ }
100
+ validate() {
101
+ const errors = this.computeErrors();
102
+ if (errors.length > 0) {
103
+ this.markInvalid(errors);
104
+ return false;
105
+ }
106
+ this.clearInvalid();
107
+ return true;
108
+ }
109
+ /** Subclasses override to add field-specific validation. */
110
+ computeErrors() {
111
+ return [];
112
+ }
113
+ markInvalid(errors) {
114
+ this._errors = Array.isArray(errors) ? errors : [errors];
115
+ if (this.el) {
116
+ this.el.classList.add("x-field-invalid");
117
+ this.showErrors();
118
+ }
119
+ this.fire("errorchange", this, this._errors);
120
+ }
121
+ clearInvalid() {
122
+ this._errors = [];
123
+ if (this.el) {
124
+ this.el.classList.remove("x-field-invalid");
125
+ this.removeErrorEl();
126
+ }
127
+ this.fire("errorchange", this, []);
128
+ }
129
+ checkValidity() {
130
+ const valid = this.isValid();
131
+ if (valid !== this._wasValid) {
132
+ this._wasValid = valid;
133
+ this.fire("validitychange", this, valid);
134
+ }
135
+ }
136
+ // -----------------------------------------------------------------------
137
+ // Error display
138
+ // -----------------------------------------------------------------------
139
+ showErrors() {
140
+ this.removeErrorEl();
141
+ if (this._errors.length === 0 || !this.el) return;
142
+ this._errorEl = document.createElement("div");
143
+ this._errorEl.classList.add("x-field-error");
144
+ this._errorEl.textContent = this._errors.join(", ");
145
+ this.el.appendChild(this._errorEl);
146
+ }
147
+ removeErrorEl() {
148
+ if (this._errorEl && this._errorEl.parentNode) {
149
+ this._errorEl.parentNode.removeChild(this._errorEl);
150
+ }
151
+ this._errorEl = null;
152
+ }
153
+ // -----------------------------------------------------------------------
154
+ // Submission
155
+ // -----------------------------------------------------------------------
156
+ getSubmitValue() {
157
+ const cfg = this._config;
158
+ if (cfg.submitValue === false) return "";
159
+ return String(this._value ?? "");
160
+ }
161
+ getName() {
162
+ return this._name;
163
+ }
164
+ };
165
+
166
+ // src/field/VTypes.ts
167
+ var VTypes = {
168
+ email: {
169
+ regex: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
170
+ test: (v) => /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(v),
171
+ text: "Not a valid email address"
172
+ },
173
+ url: {
174
+ regex: /^https?:\/\/.+/,
175
+ test: (v) => /^https?:\/\/.+/.test(v),
176
+ text: "Not a valid URL"
177
+ },
178
+ alpha: {
179
+ regex: /^[a-zA-Z]+$/,
180
+ test: (v) => /^[a-zA-Z]+$/.test(v),
181
+ text: "Only letters allowed"
182
+ },
183
+ alphanum: {
184
+ regex: /^[a-zA-Z0-9]+$/,
185
+ test: (v) => /^[a-zA-Z0-9]+$/.test(v),
186
+ text: "Only letters and numbers allowed"
187
+ }
188
+ };
189
+
190
+ // src/field/Text.ts
191
+ var SPECIAL_KEYS = /* @__PURE__ */ new Set(["Enter", "Escape", "Tab"]);
192
+ var TextField = class extends Field {
193
+ static $className = "Ext.form.field.Text";
194
+ constructor(config = {}) {
195
+ super({ xtype: "textfield", ...config });
196
+ }
197
+ afterRender() {
198
+ super.afterRender();
199
+ const cfg = this._config;
200
+ this._inputEl = document.createElement("input");
201
+ this._inputEl.type = cfg.inputType ?? "text";
202
+ this._inputEl.classList.add("x-field-input");
203
+ if (this._name) this._inputEl.name = this._name;
204
+ if (cfg.emptyText) this._inputEl.placeholder = cfg.emptyText;
205
+ if (cfg.readOnly) this._inputEl.readOnly = true;
206
+ if (cfg.disabled) this._inputEl.disabled = true;
207
+ if (cfg.maxLength) this._inputEl.maxLength = cfg.maxLength;
208
+ if (this._value !== void 0 && this._value !== null && this._value !== "") {
209
+ this._inputEl.value = String(this._value);
210
+ }
211
+ this._bodyWrapEl.appendChild(this._inputEl);
212
+ if (cfg.triggers) {
213
+ for (const trig of cfg.triggers) {
214
+ this.renderTrigger(trig);
215
+ }
216
+ }
217
+ this.attachInputEvents(cfg);
218
+ this.initFieldValidity();
219
+ }
220
+ // -----------------------------------------------------------------------
221
+ // Input element access
222
+ // -----------------------------------------------------------------------
223
+ getInputEl() {
224
+ return this._inputEl;
225
+ }
226
+ // -----------------------------------------------------------------------
227
+ // Value override — sync with DOM
228
+ // -----------------------------------------------------------------------
229
+ getValue() {
230
+ if (this._inputEl) return this._inputEl.value;
231
+ return String(this._value ?? "");
232
+ }
233
+ setValue(value) {
234
+ const old = this.getValue();
235
+ this._value = value;
236
+ if (this._inputEl) {
237
+ this._inputEl.value = String(value ?? "");
238
+ }
239
+ this.onValueChange(old, this.getValue());
240
+ }
241
+ getRawValue() {
242
+ return this._inputEl?.value ?? "";
243
+ }
244
+ setRawValue(value) {
245
+ if (this._inputEl) this._inputEl.value = value;
246
+ this._value = value;
247
+ }
248
+ getSubmitValue() {
249
+ if (this._config.submitValue === false) return "";
250
+ return this.getValue();
251
+ }
252
+ // -----------------------------------------------------------------------
253
+ // Validation
254
+ // -----------------------------------------------------------------------
255
+ computeErrors() {
256
+ const cfg = this._config;
257
+ const value = this.getValue();
258
+ const errors = [];
259
+ if (cfg.allowBlank === false && (!value || value.trim() === "")) {
260
+ errors.push("This field is required");
261
+ }
262
+ if (value && value.length > 0) {
263
+ if (cfg.minLength !== void 0 && value.length < cfg.minLength) {
264
+ errors.push(`Minimum length is ${cfg.minLength}`);
265
+ }
266
+ if (cfg.maxLength !== void 0 && value.length > cfg.maxLength) {
267
+ errors.push(`Maximum length is ${cfg.maxLength}`);
268
+ }
269
+ if (cfg.regex && !cfg.regex.test(value)) {
270
+ errors.push(cfg.regexText ?? "Invalid format");
271
+ }
272
+ if (cfg.vtype && VTypes[cfg.vtype]) {
273
+ const vt = VTypes[cfg.vtype];
274
+ if (!vt.test(value)) {
275
+ errors.push(vt.text);
276
+ }
277
+ }
278
+ }
279
+ return errors;
280
+ }
281
+ // -----------------------------------------------------------------------
282
+ // Events
283
+ // -----------------------------------------------------------------------
284
+ attachInputEvents(cfg) {
285
+ const input = this._inputEl;
286
+ input.addEventListener("input", () => {
287
+ const newVal = input.value;
288
+ const oldVal = this._value;
289
+ this._value = newVal;
290
+ if (newVal !== oldVal) {
291
+ this.fire("change", this, newVal, oldVal);
292
+ this.checkDirty();
293
+ if (cfg.validateOnChange) this.validate();
294
+ this.checkValidity();
295
+ }
296
+ });
297
+ input.addEventListener("focus", () => {
298
+ this.fire("focus", this);
299
+ });
300
+ input.addEventListener("blur", () => {
301
+ this.fire("blur", this);
302
+ if (cfg.validateOnBlur) this.validate();
303
+ if (cfg.stripCharsRe) {
304
+ input.value = input.value.replace(cfg.stripCharsRe, "");
305
+ this._value = input.value;
306
+ }
307
+ });
308
+ input.addEventListener("keydown", (e) => {
309
+ if (SPECIAL_KEYS.has(e.key)) {
310
+ this.fire("specialkey", this, e);
311
+ }
312
+ });
313
+ if (cfg.maskRe) {
314
+ const re = cfg.maskRe;
315
+ input.addEventListener("keypress", (e) => {
316
+ if (e.key.length === 1 && !re.test(e.key)) {
317
+ e.preventDefault();
318
+ }
319
+ });
320
+ }
321
+ }
322
+ // -----------------------------------------------------------------------
323
+ // Triggers
324
+ // -----------------------------------------------------------------------
325
+ renderTrigger(trig) {
326
+ const el = document.createElement("div");
327
+ el.classList.add("x-field-trigger", `x-trigger-${trig.type}`);
328
+ if (trig.cls) el.classList.add(trig.cls);
329
+ el.setAttribute("role", "button");
330
+ if (trig.handler) {
331
+ const handler = trig.handler;
332
+ el.addEventListener("click", (e) => {
333
+ handler(this, el, e);
334
+ });
335
+ }
336
+ this._bodyWrapEl.appendChild(el);
337
+ }
338
+ };
339
+
340
+ // src/field/TextArea.ts
341
+ var TextArea = class extends TextField {
342
+ static $className = "Ext.form.field.TextArea";
343
+ constructor(config = {}) {
344
+ super({ xtype: "textarea", ...config });
345
+ }
346
+ afterRender() {
347
+ const FieldProto = Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(this)));
348
+ FieldProto.afterRender?.call(this);
349
+ const cfg = this._config;
350
+ this._textareaEl = document.createElement("textarea");
351
+ this._textareaEl.classList.add("x-field-input");
352
+ if (this._name) this._textareaEl.name = this._name;
353
+ if (cfg.emptyText) this._textareaEl.placeholder = cfg.emptyText;
354
+ if (cfg.readOnly) this._textareaEl.readOnly = true;
355
+ if (cfg.disabled) this._textareaEl.disabled = true;
356
+ if (cfg.rows) this._textareaEl.rows = cfg.rows;
357
+ if (cfg.cols) this._textareaEl.cols = cfg.cols;
358
+ if (cfg.grow) {
359
+ if (cfg.growMin) this._textareaEl.style.minHeight = `${cfg.growMin}px`;
360
+ if (cfg.growMax) this._textareaEl.style.maxHeight = `${cfg.growMax}px`;
361
+ this._textareaEl.style.overflow = "auto";
362
+ }
363
+ if (this._value !== void 0 && this._value !== null && this._value !== "") {
364
+ this._textareaEl.value = String(this._value);
365
+ }
366
+ this._bodyWrapEl.appendChild(this._textareaEl);
367
+ this._inputEl = this._textareaEl;
368
+ this.attachInputEvents(cfg);
369
+ this.initFieldValidity();
370
+ }
371
+ getInputEl() {
372
+ return this._textareaEl;
373
+ }
374
+ getValue() {
375
+ if (this._textareaEl) return this._textareaEl.value;
376
+ return String(this._value ?? "");
377
+ }
378
+ setValue(value) {
379
+ const old = this.getValue();
380
+ this._value = value;
381
+ if (this._textareaEl) {
382
+ this._textareaEl.value = String(value ?? "");
383
+ }
384
+ this.onValueChange(old, this.getValue());
385
+ }
386
+ };
387
+
388
+ // src/field/Display.ts
389
+ var DisplayField = class extends Field {
390
+ static $className = "Ext.form.field.Display";
391
+ constructor(config = {}) {
392
+ super({ xtype: "displayfield", submitValue: false, ...config });
393
+ }
394
+ initialize() {
395
+ super.initialize();
396
+ this._displayEl = null;
397
+ }
398
+ afterRender() {
399
+ super.afterRender();
400
+ this._displayEl = document.createElement("div");
401
+ this._displayEl.classList.add("x-display-field-value");
402
+ this.updateDisplay();
403
+ this._bodyWrapEl.appendChild(this._displayEl);
404
+ this.el.classList.add("x-display-field");
405
+ }
406
+ setValue(value) {
407
+ const old = this._value;
408
+ this._value = value;
409
+ this.updateDisplay();
410
+ this.onValueChange(old, value);
411
+ }
412
+ getSubmitValue() {
413
+ return "";
414
+ }
415
+ updateDisplay() {
416
+ if (!this._displayEl) return;
417
+ const cfg = this._config;
418
+ const text = cfg.renderer ? cfg.renderer(this._value) : String(this._value ?? "");
419
+ this._displayEl.textContent = text;
420
+ }
421
+ };
422
+
423
+ // src/field/Hidden.ts
424
+ var HiddenField = class extends Field {
425
+ static $className = "Ext.form.field.Hidden";
426
+ constructor(config = {}) {
427
+ super({ xtype: "hiddenfield", ...config });
428
+ }
429
+ afterRender() {
430
+ const ComponentProto = Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(this)));
431
+ ComponentProto.afterRender?.call(this);
432
+ this.el.classList.add("x-field", "x-hidden-field");
433
+ this.el.style.display = "none";
434
+ this._hiddenInput = document.createElement("input");
435
+ this._hiddenInput.type = "hidden";
436
+ if (this._name) this._hiddenInput.name = this._name;
437
+ if (this._value !== void 0 && this._value !== null) {
438
+ this._hiddenInput.value = String(this._value);
439
+ }
440
+ this.el.appendChild(this._hiddenInput);
441
+ }
442
+ getValue() {
443
+ return this._hiddenInput?.value ?? String(this._value ?? "");
444
+ }
445
+ setValue(value) {
446
+ const old = this.getValue();
447
+ this._value = value;
448
+ if (this._hiddenInput) this._hiddenInput.value = String(value ?? "");
449
+ this.onValueChange(old, this.getValue());
450
+ }
451
+ getSubmitValue() {
452
+ return this.getValue();
453
+ }
454
+ };
455
+
456
+ // src/field/Number.ts
457
+ var NumberField = class extends TextField {
458
+ static $className = "Ext.form.field.Number";
459
+ constructor(config = {}) {
460
+ const triggers = [
461
+ { type: "spinner-up", handler: (_f) => this.spinUp() },
462
+ { type: "spinner-down", handler: (_f) => this.spinDown() },
463
+ ...config.triggers ?? []
464
+ ];
465
+ super({ xtype: "numberfield", ...config, triggers, maskRe: config.maskRe ?? /[0-9.\-]/ });
466
+ }
467
+ initialize() {
468
+ super.initialize();
469
+ const cfg = this._config;
470
+ this._minValue = cfg.minValue;
471
+ this._maxValue = cfg.maxValue;
472
+ this._step = cfg.step ?? 1;
473
+ this._allowDecimals = cfg.allowDecimals ?? true;
474
+ this._decimalPrecision = cfg.decimalPrecision ?? 10;
475
+ }
476
+ afterRender() {
477
+ super.afterRender();
478
+ this.el.classList.add("x-numberfield");
479
+ if (this._config.value !== void 0 && this._config.value !== null) {
480
+ this.formatInputValue();
481
+ }
482
+ }
483
+ // -----------------------------------------------------------------------
484
+ // Value
485
+ // -----------------------------------------------------------------------
486
+ getNumberValue() {
487
+ const raw = this.getInputEl()?.value ?? "";
488
+ const n = parseFloat(raw);
489
+ return isNaN(n) ? 0 : n;
490
+ }
491
+ setValue(value) {
492
+ super.setValue(value);
493
+ this.formatInputValue();
494
+ }
495
+ formatInputValue() {
496
+ if (!this._inputEl) return;
497
+ const n = parseFloat(this._inputEl.value);
498
+ if (!isNaN(n) && this._decimalPrecision < 10) {
499
+ this._inputEl.value = n.toFixed(this._decimalPrecision);
500
+ }
501
+ }
502
+ // -----------------------------------------------------------------------
503
+ // Spin
504
+ // -----------------------------------------------------------------------
505
+ spinUp() {
506
+ let val = this.getNumberValue() + this._step;
507
+ if (this._maxValue !== void 0 && val > this._maxValue) val = this._maxValue;
508
+ this.setValue(val);
509
+ this.fire("spin", this, val, "up");
510
+ }
511
+ spinDown() {
512
+ let val = this.getNumberValue() - this._step;
513
+ if (this._minValue !== void 0 && val < this._minValue) val = this._minValue;
514
+ this.setValue(val);
515
+ this.fire("spin", this, val, "down");
516
+ }
517
+ // -----------------------------------------------------------------------
518
+ // Constraints
519
+ // -----------------------------------------------------------------------
520
+ setMinValue(value) {
521
+ this._minValue = value;
522
+ }
523
+ setMaxValue(value) {
524
+ this._maxValue = value;
525
+ }
526
+ // -----------------------------------------------------------------------
527
+ // Validation
528
+ // -----------------------------------------------------------------------
529
+ computeErrors() {
530
+ const errors = super.computeErrors();
531
+ const raw = this.getInputEl()?.value ?? "";
532
+ if (raw === "") return errors;
533
+ const num = parseFloat(raw);
534
+ if (isNaN(num)) {
535
+ errors.push("Not a valid number");
536
+ return errors;
537
+ }
538
+ if (!this._allowDecimals && raw.includes(".")) {
539
+ errors.push("Decimal values are not allowed");
540
+ }
541
+ if (this._minValue !== void 0 && num < this._minValue) {
542
+ errors.push(`Value must be at least ${this._minValue}`);
543
+ }
544
+ if (this._maxValue !== void 0 && num > this._maxValue) {
545
+ errors.push(`Value must be at most ${this._maxValue}`);
546
+ }
547
+ return errors;
548
+ }
549
+ };
550
+
551
+ // src/util/DateUtil.ts
552
+ var MONTH_NAMES = [
553
+ "January",
554
+ "February",
555
+ "March",
556
+ "April",
557
+ "May",
558
+ "June",
559
+ "July",
560
+ "August",
561
+ "September",
562
+ "October",
563
+ "November",
564
+ "December"
565
+ ];
566
+ var MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
567
+ function pad(n, len = 2) {
568
+ return String(n).padStart(len, "0");
569
+ }
570
+ function formatDate(date, format2) {
571
+ const Y = date.getFullYear();
572
+ const m = date.getMonth();
573
+ const d = date.getDate();
574
+ const H = date.getHours();
575
+ const i = date.getMinutes();
576
+ const s = date.getSeconds();
577
+ const h12 = H % 12 || 12;
578
+ const ampm = H < 12 ? "AM" : "PM";
579
+ let result = "";
580
+ for (let idx = 0; idx < format2.length; idx++) {
581
+ const ch = format2[idx];
582
+ switch (ch) {
583
+ case "Y":
584
+ result += String(Y);
585
+ break;
586
+ case "m":
587
+ result += pad(m + 1);
588
+ break;
589
+ case "d":
590
+ result += pad(d);
591
+ break;
592
+ case "H":
593
+ result += pad(H);
594
+ break;
595
+ case "i":
596
+ result += pad(i);
597
+ break;
598
+ case "s":
599
+ result += pad(s);
600
+ break;
601
+ case "g":
602
+ result += String(h12);
603
+ break;
604
+ case "A":
605
+ result += ampm;
606
+ break;
607
+ case "F":
608
+ result += MONTH_NAMES[m];
609
+ break;
610
+ case "M":
611
+ result += MONTH_SHORT[m];
612
+ break;
613
+ case "j":
614
+ result += String(d);
615
+ break;
616
+ case "n":
617
+ result += String(m + 1);
618
+ break;
619
+ default:
620
+ result += ch;
621
+ break;
622
+ }
623
+ }
624
+ return result;
625
+ }
626
+ function parseDate(input, format2) {
627
+ let year = 0, month = 0, day = 1, hour = 0, minute = 0, second = 0;
628
+ let inputIdx = 0;
629
+ for (let fmtIdx = 0; fmtIdx < format2.length && inputIdx < input.length; fmtIdx++) {
630
+ const ch = format2[fmtIdx];
631
+ switch (ch) {
632
+ case "Y":
633
+ year = parseInt(input.slice(inputIdx, inputIdx + 4), 10);
634
+ inputIdx += 4;
635
+ break;
636
+ case "m":
637
+ month = parseInt(input.slice(inputIdx, inputIdx + 2), 10) - 1;
638
+ inputIdx += 2;
639
+ break;
640
+ case "d":
641
+ day = parseInt(input.slice(inputIdx, inputIdx + 2), 10);
642
+ inputIdx += 2;
643
+ break;
644
+ case "H":
645
+ hour = parseInt(input.slice(inputIdx, inputIdx + 2), 10);
646
+ inputIdx += 2;
647
+ break;
648
+ case "i":
649
+ minute = parseInt(input.slice(inputIdx, inputIdx + 2), 10);
650
+ inputIdx += 2;
651
+ break;
652
+ case "s":
653
+ second = parseInt(input.slice(inputIdx, inputIdx + 2), 10);
654
+ inputIdx += 2;
655
+ break;
656
+ default:
657
+ inputIdx++;
658
+ break;
659
+ }
660
+ }
661
+ return new Date(year, month, day, hour, minute, second);
662
+ }
663
+ function addDate(date, unit, amount) {
664
+ const d = new Date(date.getTime());
665
+ switch (unit) {
666
+ case "year":
667
+ d.setFullYear(d.getFullYear() + amount);
668
+ break;
669
+ case "month":
670
+ d.setMonth(d.getMonth() + amount);
671
+ break;
672
+ case "day":
673
+ d.setDate(d.getDate() + amount);
674
+ break;
675
+ case "hour":
676
+ d.setHours(d.getHours() + amount);
677
+ break;
678
+ case "minute":
679
+ d.setMinutes(d.getMinutes() + amount);
680
+ break;
681
+ case "second":
682
+ d.setSeconds(d.getSeconds() + amount);
683
+ break;
684
+ }
685
+ return d;
686
+ }
687
+ function diffDate(date1, date2, unit) {
688
+ const ms = date2.getTime() - date1.getTime();
689
+ switch (unit) {
690
+ case "second":
691
+ return Math.round(ms / 1e3);
692
+ case "minute":
693
+ return Math.round(ms / 6e4);
694
+ case "hour":
695
+ return Math.round(ms / 36e5);
696
+ case "day":
697
+ return Math.round(ms / 864e5);
698
+ case "month":
699
+ return (date2.getFullYear() - date1.getFullYear()) * 12 + (date2.getMonth() - date1.getMonth());
700
+ case "year":
701
+ return date2.getFullYear() - date1.getFullYear();
702
+ }
703
+ }
704
+ function isLeapYear(year) {
705
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
706
+ }
707
+ function getDaysInMonth(year, month) {
708
+ return new Date(year, month + 1, 0).getDate();
709
+ }
710
+ function getMonthName(month) {
711
+ return MONTH_NAMES[month] ?? "";
712
+ }
713
+ function getMonthShortName(month) {
714
+ return MONTH_SHORT[month] ?? "";
715
+ }
716
+
717
+ // src/field/Date.ts
718
+ var DateField = class extends TextField {
719
+ static $className = "Ext.form.field.Date";
720
+ constructor(config = {}) {
721
+ const triggers = [
722
+ { type: "expand", handler: (_f) => {
723
+ } },
724
+ ...config.triggers ?? []
725
+ ];
726
+ super({ xtype: "datefield", ...config, triggers });
727
+ }
728
+ initialize() {
729
+ super.initialize();
730
+ const cfg = this._config;
731
+ this._format = cfg.format ?? "Y-m-d";
732
+ this._minDate = cfg.minValue;
733
+ this._maxDate = cfg.maxValue;
734
+ this._dateValue = cfg.value instanceof Date ? cfg.value : null;
735
+ }
736
+ afterRender() {
737
+ super.afterRender();
738
+ this.el.classList.add("x-datefield");
739
+ if (this._dateValue) {
740
+ this._inputEl.value = formatDate(this._dateValue, this._format);
741
+ }
742
+ }
743
+ // -----------------------------------------------------------------------
744
+ // Value
745
+ // -----------------------------------------------------------------------
746
+ getDateValue() {
747
+ return this._dateValue;
748
+ }
749
+ setValue(value) {
750
+ if (value instanceof Date) {
751
+ this._dateValue = value;
752
+ const formatted = formatDate(value, this._format);
753
+ this._value = formatted;
754
+ if (this._inputEl) this._inputEl.value = formatted;
755
+ this.onValueChange(null, value);
756
+ } else if (typeof value === "string" && value) {
757
+ this._dateValue = parseDate(value, this._format);
758
+ this._value = value;
759
+ if (this._inputEl) this._inputEl.value = value;
760
+ this.onValueChange(null, this._dateValue);
761
+ } else {
762
+ this._dateValue = null;
763
+ this._value = "";
764
+ if (this._inputEl) this._inputEl.value = "";
765
+ }
766
+ }
767
+ // -----------------------------------------------------------------------
768
+ // Constraints
769
+ // -----------------------------------------------------------------------
770
+ setMinValue(date) {
771
+ this._minDate = date;
772
+ }
773
+ setMaxValue(date) {
774
+ this._maxDate = date;
775
+ }
776
+ // -----------------------------------------------------------------------
777
+ // Validation
778
+ // -----------------------------------------------------------------------
779
+ computeErrors() {
780
+ const errors = super.computeErrors();
781
+ const d = this._dateValue;
782
+ if (!d) return errors;
783
+ if (this._minDate && d.getTime() < this._minDate.getTime()) {
784
+ errors.push(`Date must be on or after ${formatDate(this._minDate, this._format)}`);
785
+ }
786
+ if (this._maxDate && d.getTime() > this._maxDate.getTime()) {
787
+ errors.push(`Date must be on or before ${formatDate(this._maxDate, this._format)}`);
788
+ }
789
+ return errors;
790
+ }
791
+ };
792
+
793
+ // src/picker/DatePicker.ts
794
+ import { Component as Component2 } from "@framesquared/component";
795
+ var DatePicker = class extends Component2 {
796
+ static $className = "Ext.picker.Date";
797
+ constructor(config = {}) {
798
+ super({ xtype: "datepicker", ...config });
799
+ }
800
+ initialize() {
801
+ super.initialize();
802
+ const cfg = this._config;
803
+ const now = cfg.value ?? /* @__PURE__ */ new Date();
804
+ this._viewDate = new Date(now.getFullYear(), now.getMonth(), 1);
805
+ this._selectedDate = new Date(now.getTime());
806
+ this._minDate = cfg.minValue;
807
+ this._maxDate = cfg.maxValue;
808
+ this._disabledDays = new Set(cfg.disabledDays ?? []);
809
+ this._gridEl = null;
810
+ this._headerEl = null;
811
+ }
812
+ afterRender() {
813
+ super.afterRender();
814
+ this.el.classList.add("x-datepicker");
815
+ this.buildUI();
816
+ }
817
+ // -----------------------------------------------------------------------
818
+ // UI Build
819
+ // -----------------------------------------------------------------------
820
+ buildUI() {
821
+ const el = this.el;
822
+ el.innerHTML = "";
823
+ const nav = document.createElement("div");
824
+ nav.classList.add("x-datepicker-nav");
825
+ const prevBtn = document.createElement("button");
826
+ prevBtn.classList.add("x-datepicker-prev");
827
+ prevBtn.type = "button";
828
+ prevBtn.textContent = "\u2039";
829
+ prevBtn.addEventListener("click", () => this.navigate(-1));
830
+ this._headerEl = document.createElement("span");
831
+ this._headerEl.classList.add("x-datepicker-header");
832
+ const nextBtn = document.createElement("button");
833
+ nextBtn.classList.add("x-datepicker-next");
834
+ nextBtn.type = "button";
835
+ nextBtn.textContent = "\u203A";
836
+ nextBtn.addEventListener("click", () => this.navigate(1));
837
+ nav.appendChild(prevBtn);
838
+ nav.appendChild(this._headerEl);
839
+ nav.appendChild(nextBtn);
840
+ el.appendChild(nav);
841
+ const dayHeaders = document.createElement("div");
842
+ dayHeaders.classList.add("x-datepicker-days");
843
+ for (const d of ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]) {
844
+ const dh = document.createElement("span");
845
+ dh.classList.add("x-datepicker-dayname");
846
+ dh.textContent = d;
847
+ dayHeaders.appendChild(dh);
848
+ }
849
+ el.appendChild(dayHeaders);
850
+ this._gridEl = document.createElement("div");
851
+ this._gridEl.classList.add("x-datepicker-grid");
852
+ el.appendChild(this._gridEl);
853
+ const cfg = this._config;
854
+ if (cfg.showToday !== false) {
855
+ const todayBtn = document.createElement("button");
856
+ todayBtn.classList.add("x-datepicker-today");
857
+ todayBtn.type = "button";
858
+ todayBtn.textContent = "Today";
859
+ todayBtn.addEventListener("click", () => {
860
+ const today = /* @__PURE__ */ new Date();
861
+ this._selectedDate = today;
862
+ this._viewDate = new Date(today.getFullYear(), today.getMonth(), 1);
863
+ this.renderGrid();
864
+ this.fire("select", this, today);
865
+ });
866
+ el.appendChild(todayBtn);
867
+ }
868
+ this.renderGrid();
869
+ }
870
+ renderGrid() {
871
+ if (!this._gridEl || !this._headerEl) return;
872
+ const year = this._viewDate.getFullYear();
873
+ const month = this._viewDate.getMonth();
874
+ this._headerEl.textContent = `${getMonthName(month)} ${year}`;
875
+ this._gridEl.innerHTML = "";
876
+ const daysInMonth = getDaysInMonth(year, month);
877
+ const firstDay = new Date(year, month, 1).getDay();
878
+ for (let i = 0; i < firstDay; i++) {
879
+ const blank = document.createElement("span");
880
+ blank.classList.add("x-datepicker-blank");
881
+ this._gridEl.appendChild(blank);
882
+ }
883
+ for (let day = 1; day <= daysInMonth; day++) {
884
+ const cellDate = new Date(year, month, day);
885
+ const cell = document.createElement("span");
886
+ cell.classList.add("x-datepicker-cell");
887
+ cell.textContent = String(day);
888
+ cell.setAttribute("data-date", `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`);
889
+ const disabled = this.isDateDisabled(cellDate);
890
+ if (disabled) {
891
+ cell.classList.add("x-datepicker-disabled");
892
+ }
893
+ if (this._selectedDate && cellDate.getFullYear() === this._selectedDate.getFullYear() && cellDate.getMonth() === this._selectedDate.getMonth() && cellDate.getDate() === this._selectedDate.getDate()) {
894
+ cell.classList.add("x-datepicker-selected");
895
+ }
896
+ cell.addEventListener("click", () => {
897
+ if (disabled) return;
898
+ this._selectedDate = cellDate;
899
+ this.renderGrid();
900
+ this.fire("select", this, cellDate);
901
+ });
902
+ this._gridEl.appendChild(cell);
903
+ }
904
+ }
905
+ // -----------------------------------------------------------------------
906
+ // Navigation
907
+ // -----------------------------------------------------------------------
908
+ navigate(delta) {
909
+ this._viewDate.setMonth(this._viewDate.getMonth() + delta);
910
+ this.renderGrid();
911
+ }
912
+ // -----------------------------------------------------------------------
913
+ // Disabled date check
914
+ // -----------------------------------------------------------------------
915
+ isDateDisabled(date) {
916
+ if (this._disabledDays.has(date.getDay())) return true;
917
+ if (this._minDate && date.getTime() < this.startOfDay(this._minDate).getTime()) return true;
918
+ if (this._maxDate && date.getTime() > this.startOfDay(this._maxDate).getTime()) return true;
919
+ return false;
920
+ }
921
+ startOfDay(d) {
922
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
923
+ }
924
+ // -----------------------------------------------------------------------
925
+ // Public
926
+ // -----------------------------------------------------------------------
927
+ getSelectedDate() {
928
+ return new Date(this._selectedDate.getTime());
929
+ }
930
+ };
931
+
932
+ // src/picker/ColorPicker.ts
933
+ import { Component as Component3 } from "@framesquared/component";
934
+ var DEFAULT_COLORS = [
935
+ "000000",
936
+ "993300",
937
+ "333300",
938
+ "003300",
939
+ "003366",
940
+ "000080",
941
+ "333399",
942
+ "333333",
943
+ "800000",
944
+ "FF6600",
945
+ "808000",
946
+ "008000",
947
+ "008080",
948
+ "0000FF",
949
+ "666699",
950
+ "808080",
951
+ "FF0000",
952
+ "FF9900",
953
+ "99CC00",
954
+ "339966",
955
+ "33CCCC",
956
+ "3366FF",
957
+ "800080",
958
+ "969696",
959
+ "FF00FF",
960
+ "FFCC00",
961
+ "FFFF00",
962
+ "00FF00",
963
+ "00FFFF",
964
+ "00CCFF",
965
+ "993366",
966
+ "C0C0C0",
967
+ "FF99CC",
968
+ "FFCC99",
969
+ "FFFF99",
970
+ "CCFFCC",
971
+ "CCFFFF",
972
+ "99CCFF",
973
+ "CC99FF",
974
+ "FFFFFF"
975
+ ];
976
+ var ColorPicker = class extends Component3 {
977
+ static $className = "Ext.picker.Color";
978
+ constructor(config = {}) {
979
+ super({ xtype: "colorpicker", ...config });
980
+ }
981
+ initialize() {
982
+ super.initialize();
983
+ const cfg = this._config;
984
+ this._colors = cfg.colors ?? DEFAULT_COLORS;
985
+ this._selectedColor = null;
986
+ }
987
+ afterRender() {
988
+ super.afterRender();
989
+ this.el.classList.add("x-colorpicker");
990
+ this.buildSwatches();
991
+ }
992
+ buildSwatches() {
993
+ const el = this.el;
994
+ for (const color of this._colors) {
995
+ const swatch = document.createElement("div");
996
+ swatch.classList.add("x-colorpicker-swatch");
997
+ swatch.style.backgroundColor = `#${color}`;
998
+ swatch.setAttribute("data-color", color);
999
+ swatch.addEventListener("click", () => {
1000
+ this._selectedColor = color;
1001
+ this.fire("select", this, color);
1002
+ });
1003
+ el.appendChild(swatch);
1004
+ }
1005
+ }
1006
+ getSelectedColor() {
1007
+ return this._selectedColor;
1008
+ }
1009
+ };
1010
+
1011
+ // src/BoundList.ts
1012
+ import { Component as Component4 } from "@framesquared/component";
1013
+ var BoundList = class extends Component4 {
1014
+ static $className = "Ext.form.BoundList";
1015
+ constructor(config = {}) {
1016
+ super({ xtype: "boundlist", ...config });
1017
+ }
1018
+ initialize() {
1019
+ super.initialize();
1020
+ const cfg = this._config;
1021
+ this._store = cfg.store ?? null;
1022
+ this._displayField = cfg.displayField ?? "text";
1023
+ this._highlightIndex = -1;
1024
+ }
1025
+ afterRender() {
1026
+ super.afterRender();
1027
+ this.el.classList.add("x-boundlist");
1028
+ this.el.setAttribute("role", "listbox");
1029
+ this.el.tabIndex = -1;
1030
+ this.renderItems();
1031
+ this.el.addEventListener("keydown", (e) => {
1032
+ this.onKeyDown(e);
1033
+ });
1034
+ }
1035
+ refresh() {
1036
+ this.renderItems();
1037
+ }
1038
+ renderItems() {
1039
+ if (!this.el) return;
1040
+ this.el.innerHTML = "";
1041
+ this._highlightIndex = -1;
1042
+ const records = Array.isArray(this._store) ? this._store : this._store?.getRange?.() ?? [];
1043
+ for (let i = 0; i < records.length; i++) {
1044
+ const rec = records[i];
1045
+ const item = document.createElement("div");
1046
+ item.classList.add("x-boundlist-item");
1047
+ item.setAttribute("role", "option");
1048
+ item.setAttribute("data-index", String(i));
1049
+ item.textContent = rec.get ? rec.get(this._displayField) : String(rec.data?.[this._displayField] ?? rec[this._displayField] ?? "");
1050
+ item.addEventListener("click", () => {
1051
+ this.fire("itemclick", this, rec, item, i);
1052
+ this.fire("select", this, rec);
1053
+ });
1054
+ item.addEventListener("mouseover", () => {
1055
+ this.highlightItem(i);
1056
+ });
1057
+ this.el.appendChild(item);
1058
+ }
1059
+ }
1060
+ highlightItem(index) {
1061
+ const items = this.el.querySelectorAll(".x-boundlist-item");
1062
+ items.forEach((el, i) => {
1063
+ el.classList.toggle("x-boundlist-item-over", i === index);
1064
+ });
1065
+ this._highlightIndex = index;
1066
+ }
1067
+ onKeyDown(e) {
1068
+ const items = this.el.querySelectorAll(".x-boundlist-item");
1069
+ const count = items.length;
1070
+ if (count === 0) return;
1071
+ if (e.key === "ArrowDown") {
1072
+ e.preventDefault();
1073
+ this.highlightItem(Math.min(this._highlightIndex + 1, count - 1));
1074
+ } else if (e.key === "ArrowUp") {
1075
+ e.preventDefault();
1076
+ this.highlightItem(Math.max(this._highlightIndex - 1, 0));
1077
+ } else if (e.key === "Enter" && this._highlightIndex >= 0) {
1078
+ e.preventDefault();
1079
+ const records = Array.isArray(this._store) ? this._store : this._store?.getRange?.() ?? [];
1080
+ const rec = records[this._highlightIndex];
1081
+ if (rec) {
1082
+ this.fire("select", this, rec);
1083
+ }
1084
+ }
1085
+ }
1086
+ getStore() {
1087
+ return this._store;
1088
+ }
1089
+ };
1090
+
1091
+ // src/field/ComboBox.ts
1092
+ var ComboBox = class extends TextField {
1093
+ static $className = "Ext.form.field.ComboBox";
1094
+ constructor(config = {}) {
1095
+ const triggers = [
1096
+ { type: "expand", handler: (_f) => {
1097
+ if (this._expanded) this.collapse();
1098
+ else this.expand();
1099
+ } },
1100
+ ...config.triggers ?? []
1101
+ ];
1102
+ super({ xtype: "combobox", ...config, triggers });
1103
+ }
1104
+ initialize() {
1105
+ super.initialize();
1106
+ const cfg = this._config;
1107
+ this._store = cfg.store ?? null;
1108
+ this._displayField = cfg.displayField ?? "text";
1109
+ this._valueField = cfg.valueField ?? "id";
1110
+ this._queryMode = cfg.queryMode ?? "local";
1111
+ this._forceSelection = cfg.forceSelection ?? false;
1112
+ this._multiSelect = cfg.multiSelect ?? false;
1113
+ this._delimiter = cfg.delimiter ?? ", ";
1114
+ this._expanded = false;
1115
+ this._selection = [];
1116
+ this._boundList = null;
1117
+ this._selectedValue = null;
1118
+ }
1119
+ afterRender() {
1120
+ super.afterRender();
1121
+ this.el.classList.add("x-combobox");
1122
+ if (this._forceSelection) {
1123
+ this.getInputEl().addEventListener("blur", () => {
1124
+ const raw = this.getInputEl().value;
1125
+ if (raw && !this.findRecord(this._displayField, raw)) {
1126
+ this.getInputEl().value = "";
1127
+ this._selectedValue = null;
1128
+ }
1129
+ });
1130
+ }
1131
+ }
1132
+ // -----------------------------------------------------------------------
1133
+ // Expand / Collapse
1134
+ // -----------------------------------------------------------------------
1135
+ expand() {
1136
+ if (this._expanded) return;
1137
+ this._expanded = true;
1138
+ if (!this._boundList) {
1139
+ this._boundList = new BoundList({
1140
+ store: this._store,
1141
+ displayField: this._displayField,
1142
+ renderTo: document.body
1143
+ });
1144
+ const listEl2 = this._boundList.el;
1145
+ listEl2.classList.add("x-combobox-list");
1146
+ listEl2.style.display = "none";
1147
+ listEl2.style.position = "fixed";
1148
+ listEl2.style.zIndex = "1000";
1149
+ listEl2.style.backgroundColor = "#fff";
1150
+ listEl2.style.border = "1px solid #ccc";
1151
+ listEl2.style.boxShadow = "0 2px 8px rgba(0,0,0,0.15)";
1152
+ listEl2.style.minWidth = "100px";
1153
+ listEl2.style.maxHeight = "200px";
1154
+ listEl2.style.overflowY = "auto";
1155
+ this._boundList.on?.("select", (_bl, rec) => {
1156
+ this.select(rec);
1157
+ this.collapse();
1158
+ });
1159
+ } else {
1160
+ this._boundList.refresh();
1161
+ }
1162
+ const anchor = this.el ?? this.getInputEl();
1163
+ const rect = anchor.getBoundingClientRect();
1164
+ const listEl = this._boundList.el;
1165
+ listEl.style.top = `${rect.bottom}px`;
1166
+ listEl.style.left = `${rect.left}px`;
1167
+ listEl.style.width = `${rect.width}px`;
1168
+ listEl.style.display = "";
1169
+ this.fire("expand", this);
1170
+ }
1171
+ collapse() {
1172
+ if (!this._expanded) return;
1173
+ this._expanded = false;
1174
+ if (this._boundList?.el) this._boundList.el.style.display = "none";
1175
+ this.fire("collapse", this);
1176
+ }
1177
+ isExpanded() {
1178
+ return this._expanded;
1179
+ }
1180
+ // -----------------------------------------------------------------------
1181
+ // Selection
1182
+ // -----------------------------------------------------------------------
1183
+ select(record) {
1184
+ if (!record) return;
1185
+ this.fire("beforeselect", this, record);
1186
+ if (this._multiSelect) {
1187
+ if (!this._selection.includes(record)) {
1188
+ this._selection.push(record);
1189
+ }
1190
+ this.updateMultiDisplay();
1191
+ } else {
1192
+ this._selection = [record];
1193
+ const display = record.get ? record.get(this._displayField) : record[this._displayField] ?? "";
1194
+ const value = record.get ? record.get(this._valueField) : record[this._valueField] ?? "";
1195
+ this.getInputEl().value = display;
1196
+ this._selectedValue = value;
1197
+ this._value = value;
1198
+ }
1199
+ this.fire("select", this, record);
1200
+ }
1201
+ deselect(record) {
1202
+ this.fire("beforedeselect", this, record);
1203
+ const idx = this._selection.indexOf(record);
1204
+ if (idx !== -1) {
1205
+ this._selection.splice(idx, 1);
1206
+ if (this._multiSelect) this.updateMultiDisplay();
1207
+ }
1208
+ }
1209
+ getSelection() {
1210
+ return [...this._selection];
1211
+ }
1212
+ updateMultiDisplay() {
1213
+ const display = this._selection.map((r) => r.get ? r.get(this._displayField) : r[this._displayField] ?? "").join(this._delimiter);
1214
+ this.getInputEl().value = display;
1215
+ this._value = this._selection.map((r) => r.get ? r.get(this._valueField) : r[this._valueField] ?? null);
1216
+ }
1217
+ // -----------------------------------------------------------------------
1218
+ // Query
1219
+ // -----------------------------------------------------------------------
1220
+ doQuery(queryString) {
1221
+ this.fire("beforequery", this, queryString);
1222
+ if (this._queryMode === "local" && this._store) {
1223
+ this._store.filter(this._displayField, queryString);
1224
+ } else if (this._queryMode === "remote" && this._store) {
1225
+ const cfg = this._config;
1226
+ this._store.load({ params: { [cfg.queryParam ?? "query"]: queryString } });
1227
+ }
1228
+ }
1229
+ // -----------------------------------------------------------------------
1230
+ // Finding
1231
+ // -----------------------------------------------------------------------
1232
+ findRecord(field, value) {
1233
+ return this._store?.findRecord?.(field, value) ?? null;
1234
+ }
1235
+ // -----------------------------------------------------------------------
1236
+ // Value override
1237
+ // -----------------------------------------------------------------------
1238
+ getValue() {
1239
+ if (this._multiSelect) {
1240
+ return this._selection.map((r) => r.get ? r.get(this._valueField) : r[this._valueField] ?? null);
1241
+ }
1242
+ return this._selectedValue ?? super.getValue();
1243
+ }
1244
+ // -----------------------------------------------------------------------
1245
+ // Cleanup
1246
+ // -----------------------------------------------------------------------
1247
+ onDestroy() {
1248
+ if (this._boundList) {
1249
+ this._boundList.destroy();
1250
+ this._boundList = null;
1251
+ }
1252
+ super.onDestroy();
1253
+ }
1254
+ };
1255
+
1256
+ // src/field/Tag.ts
1257
+ var TagField = class extends ComboBox {
1258
+ static $className = "Ext.form.field.Tag";
1259
+ constructor(config = {}) {
1260
+ super({ xtype: "tagfield", multiSelect: true, ...config });
1261
+ }
1262
+ initialize() {
1263
+ super.initialize();
1264
+ this._tagWrapEl = null;
1265
+ }
1266
+ afterRender() {
1267
+ super.afterRender();
1268
+ this.el.classList.add("x-tagfield");
1269
+ this._tagWrapEl = document.createElement("div");
1270
+ this._tagWrapEl.classList.add("x-tagfield-tags");
1271
+ this._bodyWrapEl.insertBefore(this._tagWrapEl, this._bodyWrapEl.firstChild);
1272
+ }
1273
+ // -----------------------------------------------------------------------
1274
+ // Override select to render tags
1275
+ // -----------------------------------------------------------------------
1276
+ select(record) {
1277
+ if (!record) return;
1278
+ const selection = this.getSelection();
1279
+ if (selection.includes(record)) return;
1280
+ this.fire("beforeselect", this, record);
1281
+ this._selection.push(record);
1282
+ this.renderTags();
1283
+ this.getInputEl().value = "";
1284
+ this.fire("select", this, record);
1285
+ }
1286
+ deselect(record) {
1287
+ this.fire("beforedeselect", this, record);
1288
+ const sel = this._selection;
1289
+ const idx = sel.indexOf(record);
1290
+ if (idx !== -1) {
1291
+ sel.splice(idx, 1);
1292
+ this.renderTags();
1293
+ }
1294
+ }
1295
+ // -----------------------------------------------------------------------
1296
+ // Tag rendering
1297
+ // -----------------------------------------------------------------------
1298
+ renderTags() {
1299
+ if (!this._tagWrapEl) return;
1300
+ this._tagWrapEl.innerHTML = "";
1301
+ const displayField = this._displayField;
1302
+ for (const rec of this.getSelection()) {
1303
+ const tag = document.createElement("span");
1304
+ tag.classList.add("x-tagfield-tag");
1305
+ const text = document.createElement("span");
1306
+ text.classList.add("x-tagfield-tag-text");
1307
+ text.textContent = rec.get ? rec.get(displayField) : "";
1308
+ tag.appendChild(text);
1309
+ const close = document.createElement("span");
1310
+ close.classList.add("x-tagfield-tag-close");
1311
+ close.textContent = "\xD7";
1312
+ close.addEventListener("click", (e) => {
1313
+ e.stopPropagation();
1314
+ this.deselect(rec);
1315
+ });
1316
+ tag.appendChild(close);
1317
+ this._tagWrapEl.appendChild(tag);
1318
+ }
1319
+ }
1320
+ // -----------------------------------------------------------------------
1321
+ // Value override
1322
+ // -----------------------------------------------------------------------
1323
+ getValue() {
1324
+ const valueField = this._valueField;
1325
+ return this.getSelection().map((r) => r.get ? r.get(valueField) : null);
1326
+ }
1327
+ };
1328
+
1329
+ // src/field/Checkbox.ts
1330
+ var Checkbox = class extends Field {
1331
+ static $className = "Ext.form.field.Checkbox";
1332
+ constructor(config = {}) {
1333
+ super({ xtype: "checkboxfield", ...config });
1334
+ }
1335
+ initialize() {
1336
+ super.initialize();
1337
+ const cfg = this._config;
1338
+ this._checked = cfg.checked ?? false;
1339
+ this._inputValue = cfg.inputValue ?? "on";
1340
+ this._uncheckedValue = cfg.uncheckedValue ?? "";
1341
+ }
1342
+ afterRender() {
1343
+ super.afterRender();
1344
+ const cfg = this._config;
1345
+ this.el.classList.add("x-checkbox");
1346
+ this._inputEl = document.createElement("input");
1347
+ this._inputEl.type = "checkbox";
1348
+ this._inputEl.classList.add("x-checkbox-input");
1349
+ if (this._name) this._inputEl.name = this._name;
1350
+ this._inputEl.checked = this._checked;
1351
+ this._bodyWrapEl.appendChild(this._inputEl);
1352
+ if (cfg.boxLabel) {
1353
+ const label = document.createElement("span");
1354
+ label.classList.add("x-checkbox-label");
1355
+ label.textContent = cfg.boxLabel;
1356
+ this._bodyWrapEl.appendChild(label);
1357
+ }
1358
+ this._inputEl.addEventListener("change", () => {
1359
+ const prev = this._checked;
1360
+ this._checked = this._inputEl.checked;
1361
+ this.fire("change", this, this.getValue(), prev ? this._inputValue : this._uncheckedValue);
1362
+ });
1363
+ }
1364
+ // -----------------------------------------------------------------------
1365
+ // Value
1366
+ // -----------------------------------------------------------------------
1367
+ getValue() {
1368
+ return this._checked ? this._inputValue : this._uncheckedValue;
1369
+ }
1370
+ isChecked() {
1371
+ return this._checked;
1372
+ }
1373
+ setChecked(checked) {
1374
+ this._checked = checked;
1375
+ if (this._inputEl) this._inputEl.checked = checked;
1376
+ this.fire("change", this, this.getValue());
1377
+ }
1378
+ getSubmitValue() {
1379
+ return this.getValue();
1380
+ }
1381
+ };
1382
+
1383
+ // src/field/Radio.ts
1384
+ var radioGroups = /* @__PURE__ */ new Map();
1385
+ var Radio = class extends Checkbox {
1386
+ static $className = "Ext.form.field.Radio";
1387
+ constructor(config = {}) {
1388
+ super({ xtype: "radiofield", ...config });
1389
+ }
1390
+ afterRender() {
1391
+ super.afterRender();
1392
+ this._inputEl.type = "radio";
1393
+ this.el.classList.remove("x-checkbox");
1394
+ this.el.classList.add("x-radio");
1395
+ if (this._name) {
1396
+ let group = radioGroups.get(this._name);
1397
+ if (!group) {
1398
+ group = /* @__PURE__ */ new Set();
1399
+ radioGroups.set(this._name, group);
1400
+ }
1401
+ group.add(this);
1402
+ }
1403
+ }
1404
+ setChecked(checked) {
1405
+ if (checked && this._name) {
1406
+ const group = radioGroups.get(this._name);
1407
+ if (group) {
1408
+ for (const radio of group) {
1409
+ if (radio !== this && radio._checked) {
1410
+ radio._checked = false;
1411
+ if (radio._inputEl) radio._inputEl.checked = false;
1412
+ }
1413
+ }
1414
+ }
1415
+ }
1416
+ super.setChecked(checked);
1417
+ }
1418
+ getGroupValue() {
1419
+ if (this._name) {
1420
+ const group = radioGroups.get(this._name);
1421
+ if (group) {
1422
+ for (const radio of group) {
1423
+ if (radio.isChecked()) return radio._inputValue;
1424
+ }
1425
+ }
1426
+ }
1427
+ return "";
1428
+ }
1429
+ onDestroy() {
1430
+ if (this._name) {
1431
+ radioGroups.get(this._name)?.delete(this);
1432
+ }
1433
+ super.onDestroy();
1434
+ }
1435
+ };
1436
+
1437
+ // src/field/CheckboxGroup.ts
1438
+ import { Container } from "@framesquared/component";
1439
+ var CheckboxGroup = class extends Container {
1440
+ static $className = "Ext.form.CheckboxGroup";
1441
+ constructor(config = {}) {
1442
+ super({ xtype: "checkboxgroup", ...config });
1443
+ }
1444
+ afterRender() {
1445
+ super.afterRender();
1446
+ this.el.classList.add("x-checkbox-group");
1447
+ const cfg = this._config;
1448
+ if (cfg.columns) {
1449
+ const body = this.getBodyEl();
1450
+ body.style.display = "grid";
1451
+ body.style.gridTemplateColumns = `repeat(${cfg.columns}, 1fr)`;
1452
+ }
1453
+ }
1454
+ getValue() {
1455
+ return this.getItems().filter((item) => item instanceof Checkbox && item.isChecked()).map((cb) => cb.getValue());
1456
+ }
1457
+ setValue(values) {
1458
+ for (const item of this.getItems()) {
1459
+ if (item instanceof Checkbox) {
1460
+ item.setChecked(values.includes(item.getValue()) || values.includes(item._inputValue));
1461
+ }
1462
+ }
1463
+ }
1464
+ };
1465
+
1466
+ // src/field/RadioGroup.ts
1467
+ import { Container as Container2 } from "@framesquared/component";
1468
+ var RadioGroup = class extends Container2 {
1469
+ static $className = "Ext.form.RadioGroup";
1470
+ constructor(config = {}) {
1471
+ super({ xtype: "radiogroup", ...config });
1472
+ }
1473
+ afterRender() {
1474
+ super.afterRender();
1475
+ this.el.classList.add("x-radio-group");
1476
+ const cfg = this._config;
1477
+ if (cfg.columns) {
1478
+ const body = this.getBodyEl();
1479
+ body.style.display = "grid";
1480
+ body.style.gridTemplateColumns = `repeat(${cfg.columns}, 1fr)`;
1481
+ }
1482
+ }
1483
+ getValue() {
1484
+ for (const item of this.getItems()) {
1485
+ if (item instanceof Radio && item.isChecked()) {
1486
+ return item.getValue();
1487
+ }
1488
+ }
1489
+ return "";
1490
+ }
1491
+ setValue(value) {
1492
+ for (const item of this.getItems()) {
1493
+ if (item instanceof Radio) {
1494
+ item.setChecked(item._inputValue === value);
1495
+ }
1496
+ }
1497
+ }
1498
+ };
1499
+
1500
+ // src/FormPanel.ts
1501
+ import { Panel } from "@framesquared/ui";
1502
+ import { Component as Component5 } from "@framesquared/component";
1503
+
1504
+ // src/form/BasicForm.ts
1505
+ var BasicForm = class {
1506
+ fields = [];
1507
+ // -----------------------------------------------------------------------
1508
+ // Field management
1509
+ // -----------------------------------------------------------------------
1510
+ addField(field) {
1511
+ if (!this.fields.includes(field)) {
1512
+ this.fields.push(field);
1513
+ }
1514
+ }
1515
+ removeField(field) {
1516
+ const idx = this.fields.indexOf(field);
1517
+ if (idx !== -1) this.fields.splice(idx, 1);
1518
+ }
1519
+ getFields() {
1520
+ return [...this.fields];
1521
+ }
1522
+ findField(name) {
1523
+ return this.fields.find((f) => f.getName() === name);
1524
+ }
1525
+ // -----------------------------------------------------------------------
1526
+ // Values
1527
+ // -----------------------------------------------------------------------
1528
+ getValues(asString = false, dirtyOnly = false) {
1529
+ const values = {};
1530
+ for (const field of this.fields) {
1531
+ const name = field.getName();
1532
+ if (!name) continue;
1533
+ if (dirtyOnly && !field.isDirty()) continue;
1534
+ values[name] = field.getSubmitValue?.() ?? field.getValue();
1535
+ }
1536
+ if (asString) {
1537
+ return new URLSearchParams(values).toString();
1538
+ }
1539
+ return values;
1540
+ }
1541
+ setValues(values) {
1542
+ for (const [key, val] of Object.entries(values)) {
1543
+ const field = this.findField(key);
1544
+ if (field) field.setValue(val);
1545
+ }
1546
+ }
1547
+ // -----------------------------------------------------------------------
1548
+ // Validation
1549
+ // -----------------------------------------------------------------------
1550
+ isValid() {
1551
+ return this.fields.every((f) => f.isValid());
1552
+ }
1553
+ isDirty() {
1554
+ return this.fields.some((f) => f.isDirty());
1555
+ }
1556
+ reset() {
1557
+ for (const field of this.fields) field.reset();
1558
+ }
1559
+ clearInvalid() {
1560
+ for (const field of this.fields) field.clearInvalid();
1561
+ }
1562
+ markInvalid(errors) {
1563
+ for (const err of errors) {
1564
+ const field = this.findField(err.field);
1565
+ if (field) field.markInvalid(err.message);
1566
+ }
1567
+ }
1568
+ // -----------------------------------------------------------------------
1569
+ // Submit / Load
1570
+ // -----------------------------------------------------------------------
1571
+ async submit(url2, options = {}) {
1572
+ const method = options.method ?? "POST";
1573
+ const values = this.getValues();
1574
+ const params = { ...values, ...options.params ?? {} };
1575
+ let body;
1576
+ const headers = { ...options.headers ?? {} };
1577
+ if (options.jsonSubmit) {
1578
+ headers["Content-Type"] = "application/json";
1579
+ body = JSON.stringify(params);
1580
+ } else {
1581
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
1582
+ body = new URLSearchParams(params).toString();
1583
+ }
1584
+ const response = await fetch(url2, { method, headers, body });
1585
+ const data = await response.json();
1586
+ return {
1587
+ success: response.ok && data.success !== false,
1588
+ data
1589
+ };
1590
+ }
1591
+ async load(url2, options = {}) {
1592
+ const method = options.method ?? "GET";
1593
+ const response = await fetch(url2, { method });
1594
+ const data = await response.json();
1595
+ if (response.ok && data.data) {
1596
+ this.setValues(data.data);
1597
+ }
1598
+ return { success: response.ok, data };
1599
+ }
1600
+ };
1601
+
1602
+ // src/FormPanel.ts
1603
+ var FormPanel = class extends Panel {
1604
+ static $className = "Ext.form.Panel";
1605
+ constructor(config = {}) {
1606
+ if (config.fieldDefaults && config.items) {
1607
+ config.items = config.items.map((item) => {
1608
+ if (item instanceof Component5) {
1609
+ const cfg = item._config;
1610
+ for (const [k, v] of Object.entries(config.fieldDefaults)) {
1611
+ if (cfg[k] === void 0) cfg[k] = v;
1612
+ }
1613
+ return item;
1614
+ }
1615
+ return { ...config.fieldDefaults, ...item };
1616
+ });
1617
+ }
1618
+ super({ xtype: "form", ...config });
1619
+ }
1620
+ initialize() {
1621
+ super.initialize();
1622
+ const cfg = this._config;
1623
+ this._basicForm = new BasicForm();
1624
+ this._url = cfg.url ?? "";
1625
+ this._method = cfg.method ?? "POST";
1626
+ this._jsonSubmit = cfg.jsonSubmit ?? false;
1627
+ }
1628
+ afterRender() {
1629
+ super.afterRender();
1630
+ this.el.classList.add("x-form");
1631
+ this.collectFields();
1632
+ }
1633
+ // -----------------------------------------------------------------------
1634
+ // Field collection
1635
+ // -----------------------------------------------------------------------
1636
+ collectFields() {
1637
+ this.walkItems(this, (item) => {
1638
+ if (item instanceof Field) {
1639
+ this._basicForm.addField(item);
1640
+ }
1641
+ });
1642
+ }
1643
+ walkItems(container, fn) {
1644
+ const items = container.getItems?.() ?? [];
1645
+ for (const item of items) {
1646
+ fn(item);
1647
+ if (typeof item.getItems === "function") {
1648
+ this.walkItems(item, fn);
1649
+ }
1650
+ }
1651
+ }
1652
+ // -----------------------------------------------------------------------
1653
+ // Public API (delegates to BasicForm)
1654
+ // -----------------------------------------------------------------------
1655
+ getValues(asString = false, dirtyOnly = false) {
1656
+ return this._basicForm.getValues(asString, dirtyOnly);
1657
+ }
1658
+ setValues(values) {
1659
+ this._basicForm.setValues(values);
1660
+ }
1661
+ isFormValid() {
1662
+ return this._basicForm.isValid();
1663
+ }
1664
+ // Override component's isValid concept
1665
+ isValid() {
1666
+ return this._basicForm.isValid();
1667
+ }
1668
+ isDirty() {
1669
+ return this._basicForm.isDirty();
1670
+ }
1671
+ getFields() {
1672
+ return this._basicForm.getFields();
1673
+ }
1674
+ findField(name) {
1675
+ return this._basicForm.findField(name);
1676
+ }
1677
+ reset() {
1678
+ this._basicForm.reset();
1679
+ }
1680
+ clearInvalid() {
1681
+ this._basicForm.clearInvalid();
1682
+ }
1683
+ markInvalid(errors) {
1684
+ this._basicForm.markInvalid(errors);
1685
+ }
1686
+ hasInvalidField() {
1687
+ return !this._basicForm.isValid();
1688
+ }
1689
+ // -----------------------------------------------------------------------
1690
+ // Submit
1691
+ // -----------------------------------------------------------------------
1692
+ async submit(options = {}) {
1693
+ if (options.clientValidation !== false && options.clientValidation !== void 0) {
1694
+ if (!this.isValid()) {
1695
+ return { success: false, errors: "Validation failed" };
1696
+ }
1697
+ }
1698
+ const url2 = options.url ?? this._url;
1699
+ if (!url2) return { success: false, errors: "No URL specified" };
1700
+ this.fire("beforeaction", this, "submit");
1701
+ try {
1702
+ const result = await this._basicForm.submit(url2, {
1703
+ method: options.method ?? this._method,
1704
+ params: options.params,
1705
+ headers: options.headers,
1706
+ jsonSubmit: options.jsonSubmit ?? this._jsonSubmit
1707
+ });
1708
+ if (result.success) {
1709
+ this.fire("actioncomplete", this, result);
1710
+ } else {
1711
+ this.fire("actionfailed", this, result);
1712
+ }
1713
+ return result;
1714
+ } catch (err) {
1715
+ const result = { success: false, errors: err };
1716
+ this.fire("actionfailed", this, result);
1717
+ return result;
1718
+ }
1719
+ }
1720
+ // -----------------------------------------------------------------------
1721
+ // Load
1722
+ // -----------------------------------------------------------------------
1723
+ async load(options = {}) {
1724
+ const url2 = options.url ?? this._url;
1725
+ if (!url2) return;
1726
+ this.fire("beforeaction", this, "load");
1727
+ try {
1728
+ const result = await this._basicForm.load(url2, {
1729
+ method: options.method ?? "GET"
1730
+ });
1731
+ if (result.success) {
1732
+ this.fire("actioncomplete", this, result);
1733
+ } else {
1734
+ this.fire("actionfailed", this, result);
1735
+ }
1736
+ } catch (err) {
1737
+ this.fire("actionfailed", this, { success: false, errors: err });
1738
+ }
1739
+ }
1740
+ };
1741
+
1742
+ // src/form/Validators.ts
1743
+ function presence(value) {
1744
+ if (value === null || value === void 0 || String(value).trim() === "") {
1745
+ return "This field is required";
1746
+ }
1747
+ return true;
1748
+ }
1749
+ function length(value, opts) {
1750
+ const s = String(value ?? "");
1751
+ if (opts.min !== void 0 && s.length < opts.min) {
1752
+ return `Minimum length is ${opts.min}`;
1753
+ }
1754
+ if (opts.max !== void 0 && s.length > opts.max) {
1755
+ return `Maximum length is ${opts.max}`;
1756
+ }
1757
+ return true;
1758
+ }
1759
+ function email(value) {
1760
+ if (/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(String(value))) return true;
1761
+ return "Not a valid email address";
1762
+ }
1763
+ function url(value) {
1764
+ if (/^https?:\/\/.+/.test(String(value))) return true;
1765
+ return "Not a valid URL";
1766
+ }
1767
+ function alpha(value) {
1768
+ if (/^[a-zA-Z]+$/.test(String(value))) return true;
1769
+ return "Only letters are allowed";
1770
+ }
1771
+ function alphanum(value) {
1772
+ if (/^[a-zA-Z0-9]+$/.test(String(value))) return true;
1773
+ return "Only letters and numbers are allowed";
1774
+ }
1775
+ function range(value, opts) {
1776
+ const n = Number(value);
1777
+ if (opts.min !== void 0 && n < opts.min) return `Value must be at least ${opts.min}`;
1778
+ if (opts.max !== void 0 && n > opts.max) return `Value must be at most ${opts.max}`;
1779
+ return true;
1780
+ }
1781
+ function format(value, opts) {
1782
+ if (opts.matcher.test(String(value))) return true;
1783
+ return opts.message ?? "Invalid format";
1784
+ }
1785
+ function inclusion(value, opts) {
1786
+ if (opts.list.includes(value)) return true;
1787
+ return "Value is not in the allowed list";
1788
+ }
1789
+ function exclusion(value, opts) {
1790
+ if (!opts.list.includes(value)) return true;
1791
+ return "Value is in the excluded list";
1792
+ }
1793
+ function custom(value, opts) {
1794
+ return opts.fn(value);
1795
+ }
1796
+ var VALIDATORS = {
1797
+ presence: (v) => presence(v),
1798
+ length: (v, o) => length(v, o),
1799
+ email: (v) => email(v),
1800
+ url: (v) => url(v),
1801
+ alpha: (v) => alpha(v),
1802
+ alphanum: (v) => alphanum(v),
1803
+ range: (v, o) => range(v, o),
1804
+ format: (v, o) => format(v, o),
1805
+ inclusion: (v, o) => inclusion(v, o),
1806
+ exclusion: (v, o) => exclusion(v, o),
1807
+ custom: (v, o) => custom(v, o)
1808
+ };
1809
+ function validateChain(value, chain) {
1810
+ const errors = [];
1811
+ for (const entry of chain) {
1812
+ const fn = VALIDATORS[entry.type];
1813
+ if (!fn) continue;
1814
+ const result = fn(value, entry);
1815
+ if (result !== true) {
1816
+ errors.push(result);
1817
+ }
1818
+ }
1819
+ return errors;
1820
+ }
1821
+
1822
+ // src/field/FieldContainer.ts
1823
+ import { Container as Container3 } from "@framesquared/component";
1824
+ var FieldContainer = class extends Container3 {
1825
+ static $className = "Ext.form.FieldContainer";
1826
+ constructor(config = {}) {
1827
+ super({ xtype: "fieldcontainer", ...config });
1828
+ }
1829
+ afterRender() {
1830
+ super.afterRender();
1831
+ const cfg = this._config;
1832
+ this.el.classList.add("x-field-container");
1833
+ if (cfg.fieldLabel) {
1834
+ const label = document.createElement("label");
1835
+ label.classList.add("x-field-label");
1836
+ label.textContent = cfg.fieldLabel + (cfg.labelSeparator ?? ":");
1837
+ this.el.insertBefore(label, this.el.firstChild);
1838
+ }
1839
+ }
1840
+ getErrors() {
1841
+ const errors = [];
1842
+ for (const item of this.getItems()) {
1843
+ if (item instanceof Field) {
1844
+ errors.push(...item.getErrors());
1845
+ }
1846
+ }
1847
+ return errors;
1848
+ }
1849
+ isValid() {
1850
+ return this.getItems().filter((item) => item instanceof Field).every((f) => f.isValid());
1851
+ }
1852
+ };
1853
+
1854
+ // src/field/Slider.ts
1855
+ var Slider = class extends Field {
1856
+ static $className = "Ext.slider.Slider";
1857
+ constructor(config = {}) {
1858
+ super({ xtype: "sliderfield", ...config });
1859
+ }
1860
+ initialize() {
1861
+ super.initialize();
1862
+ const cfg = this._config;
1863
+ this._min = cfg.minValue ?? 0;
1864
+ this._max = cfg.maxValue ?? 100;
1865
+ this._increment = cfg.increment ?? 0;
1866
+ this._vertical = cfg.vertical ?? false;
1867
+ this._values = cfg.values ? cfg.values.map((v) => this.snap(this.clamp(v))) : [this.snap(this.clamp(Number(cfg.value ?? this._min)))];
1868
+ this._trackEl = null;
1869
+ this._thumbEls = [];
1870
+ this._dragIndex = -1;
1871
+ this._dragCleanup = null;
1872
+ }
1873
+ afterRender() {
1874
+ super.afterRender();
1875
+ const el = this.el;
1876
+ el.classList.add("x-slider");
1877
+ if (this._vertical) el.classList.add("x-slider-vertical");
1878
+ this._trackEl = document.createElement("div");
1879
+ this._trackEl.classList.add("x-slider-track");
1880
+ (this._bodyWrapEl ?? el).appendChild(this._trackEl);
1881
+ for (let i = 0; i < this._values.length; i++) {
1882
+ const thumb = document.createElement("div");
1883
+ thumb.classList.add("x-slider-thumb");
1884
+ thumb.setAttribute("tabindex", "0");
1885
+ thumb.setAttribute("role", "slider");
1886
+ thumb.setAttribute("aria-valuemin", String(this._min));
1887
+ thumb.setAttribute("aria-valuemax", String(this._max));
1888
+ thumb.setAttribute("aria-valuenow", String(this._values[i]));
1889
+ this._trackEl.appendChild(thumb);
1890
+ this._thumbEls.push(thumb);
1891
+ this.setupThumbDrag(thumb, i);
1892
+ }
1893
+ this.positionThumbs();
1894
+ }
1895
+ // -----------------------------------------------------------------------
1896
+ // Value
1897
+ // -----------------------------------------------------------------------
1898
+ getValue() {
1899
+ return this._values[0];
1900
+ }
1901
+ setValue(value) {
1902
+ const num = this.snap(this.clamp(Number(value)));
1903
+ const old = this._values[0];
1904
+ this._values[0] = num;
1905
+ this.positionThumbs();
1906
+ this.updateAria(0);
1907
+ if (num !== old) {
1908
+ this.fire("change", this, num, old);
1909
+ }
1910
+ }
1911
+ getValues() {
1912
+ return [...this._values];
1913
+ }
1914
+ // -----------------------------------------------------------------------
1915
+ // Constraints
1916
+ // -----------------------------------------------------------------------
1917
+ setMinValue(min) {
1918
+ this._min = min;
1919
+ this._values = this._values.map((v) => this.snap(this.clamp(v)));
1920
+ this.positionThumbs();
1921
+ this.updateAllAria();
1922
+ }
1923
+ setMaxValue(max) {
1924
+ this._max = max;
1925
+ this._values = this._values.map((v) => this.snap(this.clamp(v)));
1926
+ this.positionThumbs();
1927
+ this.updateAllAria();
1928
+ }
1929
+ setIncrement(inc) {
1930
+ this._increment = inc;
1931
+ }
1932
+ clamp(v) {
1933
+ return Math.max(this._min, Math.min(this._max, v));
1934
+ }
1935
+ snap(v) {
1936
+ if (this._increment <= 0) return v;
1937
+ return Math.round(v / this._increment) * this._increment;
1938
+ }
1939
+ // -----------------------------------------------------------------------
1940
+ // Positioning
1941
+ // -----------------------------------------------------------------------
1942
+ positionThumbs() {
1943
+ for (let i = 0; i < this._thumbEls.length; i++) {
1944
+ const pct = (this._values[i] - this._min) / (this._max - this._min) * 100;
1945
+ if (this._vertical) {
1946
+ this._thumbEls[i].style.bottom = `${pct}%`;
1947
+ } else {
1948
+ this._thumbEls[i].style.left = `${pct}%`;
1949
+ }
1950
+ }
1951
+ }
1952
+ updateAria(index) {
1953
+ const thumb = this._thumbEls[index];
1954
+ if (!thumb) return;
1955
+ thumb.setAttribute("aria-valuenow", String(this._values[index]));
1956
+ thumb.setAttribute("aria-valuemin", String(this._min));
1957
+ thumb.setAttribute("aria-valuemax", String(this._max));
1958
+ }
1959
+ updateAllAria() {
1960
+ for (let i = 0; i < this._thumbEls.length; i++) this.updateAria(i);
1961
+ }
1962
+ // -----------------------------------------------------------------------
1963
+ // Drag
1964
+ // -----------------------------------------------------------------------
1965
+ setupThumbDrag(thumb, index) {
1966
+ const onDown = (e) => {
1967
+ e.preventDefault();
1968
+ this._dragIndex = index;
1969
+ this.fire("dragstart", this, index);
1970
+ const onMove = (me) => {
1971
+ if (this._dragIndex < 0 || !this._trackEl) return;
1972
+ const rect = this._trackEl.getBoundingClientRect();
1973
+ let ratio;
1974
+ if (this._vertical) {
1975
+ ratio = 1 - (me.clientY - rect.top) / rect.height;
1976
+ } else {
1977
+ ratio = (me.clientX - rect.left) / rect.width;
1978
+ }
1979
+ ratio = Math.max(0, Math.min(1, ratio));
1980
+ const raw = this._min + ratio * (this._max - this._min);
1981
+ const val = this.snap(this.clamp(raw));
1982
+ const old = this._values[this._dragIndex];
1983
+ this._values[this._dragIndex] = val;
1984
+ this.positionThumbs();
1985
+ this.updateAria(this._dragIndex);
1986
+ if (val !== old) this.fire("drag", this, val);
1987
+ };
1988
+ const onUp = () => {
1989
+ document.removeEventListener("pointermove", onMove);
1990
+ document.removeEventListener("pointerup", onUp);
1991
+ this.fire("dragend", this, this._values[this._dragIndex]);
1992
+ this.fire("changecomplete", this, this._values[this._dragIndex]);
1993
+ this._dragIndex = -1;
1994
+ };
1995
+ document.addEventListener("pointermove", onMove);
1996
+ document.addEventListener("pointerup", onUp);
1997
+ };
1998
+ thumb.addEventListener("pointerdown", onDown);
1999
+ }
2000
+ };
2001
+
2002
+ // src/field/FileUpload.ts
2003
+ var FileField = class extends Field {
2004
+ static $className = "Ext.form.field.File";
2005
+ constructor(config = {}) {
2006
+ super({ xtype: "filefield", ...config });
2007
+ }
2008
+ initialize() {
2009
+ super.initialize();
2010
+ this._files = null;
2011
+ }
2012
+ afterRender() {
2013
+ super.afterRender();
2014
+ const cfg = this._config;
2015
+ this.el.classList.add("x-filefield");
2016
+ const wrap = this._bodyWrapEl ?? this.el;
2017
+ this._textDisplay = document.createElement("span");
2018
+ this._textDisplay.classList.add("x-filefield-text");
2019
+ if (cfg.buttonOnly) this._textDisplay.style.display = "none";
2020
+ wrap.appendChild(this._textDisplay);
2021
+ const btn = document.createElement("button");
2022
+ btn.type = "button";
2023
+ btn.classList.add("x-filefield-btn");
2024
+ btn.textContent = cfg.buttonText ?? "Browse...";
2025
+ btn.addEventListener("click", () => this._fileInput.click());
2026
+ wrap.appendChild(btn);
2027
+ this._fileInput = document.createElement("input");
2028
+ this._fileInput.type = "file";
2029
+ this._fileInput.style.display = "none";
2030
+ if (cfg.accept) this._fileInput.accept = cfg.accept;
2031
+ if (cfg.multiple) this._fileInput.multiple = true;
2032
+ if (this._name) this._fileInput.name = this._name;
2033
+ this._fileInput.addEventListener("change", () => {
2034
+ this._files = this._fileInput.files;
2035
+ const names = this._files ? Array.from(this._files).map((f) => f.name).join(", ") : "";
2036
+ this._textDisplay.textContent = names;
2037
+ this.fire("change", this, this._files);
2038
+ });
2039
+ wrap.appendChild(this._fileInput);
2040
+ }
2041
+ getValue() {
2042
+ return this._files;
2043
+ }
2044
+ getFileInput() {
2045
+ return this._fileInput;
2046
+ }
2047
+ };
2048
+
2049
+ // src/field/HtmlEditor.ts
2050
+ var TOOLBAR_BUTTONS = [
2051
+ { cmd: "bold", label: "B", cls: "bold" },
2052
+ { cmd: "italic", label: "I", cls: "italic" },
2053
+ { cmd: "underline", label: "U", cls: "underline" },
2054
+ { cmd: "strikeThrough", label: "S", cls: "strikethrough" },
2055
+ { cmd: "justifyLeft", label: "\u21D0", cls: "align-left" },
2056
+ { cmd: "justifyCenter", label: "\u21D4", cls: "align-center" },
2057
+ { cmd: "justifyRight", label: "\u21D2", cls: "align-right" },
2058
+ { cmd: "insertOrderedList", label: "OL", cls: "ordered-list" },
2059
+ { cmd: "insertUnorderedList", label: "UL", cls: "unordered-list" },
2060
+ { cmd: "indent", label: "\u2192", cls: "indent" },
2061
+ { cmd: "outdent", label: "\u2190", cls: "outdent" },
2062
+ { cmd: "undo", label: "\u21A9", cls: "undo" },
2063
+ { cmd: "redo", label: "\u21AA", cls: "redo" }
2064
+ ];
2065
+ var XSS_TAGS = /(<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>|<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>|on\w+="[^"]*"|on\w+='[^']*')/gi;
2066
+ var HtmlEditor = class extends Field {
2067
+ static $className = "Ext.form.field.HtmlEditor";
2068
+ constructor(config = {}) {
2069
+ super({ xtype: "htmleditor", ...config });
2070
+ }
2071
+ initialize() {
2072
+ super.initialize();
2073
+ this._toolbarEl = null;
2074
+ this._editorEl = null;
2075
+ this._sourceEl = null;
2076
+ this._sourceMode = false;
2077
+ }
2078
+ afterRender() {
2079
+ super.afterRender();
2080
+ this.el.classList.add("x-htmleditor");
2081
+ const wrap = this._bodyWrapEl ?? this.el;
2082
+ this._toolbarEl = document.createElement("div");
2083
+ this._toolbarEl.classList.add("x-htmleditor-toolbar");
2084
+ for (const btn of TOOLBAR_BUTTONS) {
2085
+ const el = document.createElement("button");
2086
+ el.type = "button";
2087
+ el.classList.add("x-htmleditor-btn", `x-htmleditor-btn-${btn.cls}`);
2088
+ el.textContent = btn.label;
2089
+ el.title = btn.cmd;
2090
+ el.addEventListener("click", (e) => {
2091
+ e.preventDefault();
2092
+ this.execCommand(btn.cmd);
2093
+ });
2094
+ this._toolbarEl.appendChild(el);
2095
+ }
2096
+ const srcBtn = document.createElement("button");
2097
+ srcBtn.type = "button";
2098
+ srcBtn.classList.add("x-htmleditor-btn", "x-htmleditor-btn-source");
2099
+ srcBtn.textContent = "HTML";
2100
+ srcBtn.addEventListener("click", () => this.toggleSourceEdit());
2101
+ this._toolbarEl.appendChild(srcBtn);
2102
+ wrap.appendChild(this._toolbarEl);
2103
+ this._editorEl = document.createElement("div");
2104
+ this._editorEl.classList.add("x-htmleditor-body");
2105
+ this._editorEl.contentEditable = "true";
2106
+ this._editorEl.style.minHeight = "100px";
2107
+ if (this._value) {
2108
+ this._editorEl.innerHTML = this.sanitize(String(this._value));
2109
+ }
2110
+ this._editorEl.addEventListener("input", () => {
2111
+ this.fire("change", this, this.getValue());
2112
+ });
2113
+ wrap.appendChild(this._editorEl);
2114
+ this._sourceEl = document.createElement("textarea");
2115
+ this._sourceEl.classList.add("x-htmleditor-source");
2116
+ this._sourceEl.style.display = "none";
2117
+ this._sourceEl.style.width = "100%";
2118
+ this._sourceEl.style.minHeight = "100px";
2119
+ wrap.appendChild(this._sourceEl);
2120
+ }
2121
+ // -----------------------------------------------------------------------
2122
+ // Value
2123
+ // -----------------------------------------------------------------------
2124
+ getValue() {
2125
+ if (this._sourceMode && this._sourceEl) {
2126
+ return this.sanitize(this._sourceEl.value);
2127
+ }
2128
+ return this._editorEl ? this.sanitize(this._editorEl.innerHTML) : "";
2129
+ }
2130
+ setValue(value) {
2131
+ const html = this.sanitize(String(value ?? ""));
2132
+ this._value = html;
2133
+ if (this._editorEl) this._editorEl.innerHTML = html;
2134
+ if (this._sourceEl) this._sourceEl.value = html;
2135
+ }
2136
+ // -----------------------------------------------------------------------
2137
+ // Commands
2138
+ // -----------------------------------------------------------------------
2139
+ execCommand(cmd, value) {
2140
+ if (this._sourceMode) return;
2141
+ try {
2142
+ document.execCommand(cmd, false, value ?? "");
2143
+ } catch {
2144
+ }
2145
+ this._editorEl?.focus();
2146
+ }
2147
+ // -----------------------------------------------------------------------
2148
+ // Source edit
2149
+ // -----------------------------------------------------------------------
2150
+ toggleSourceEdit() {
2151
+ this._sourceMode = !this._sourceMode;
2152
+ if (this._sourceMode) {
2153
+ if (this._sourceEl && this._editorEl) {
2154
+ this._sourceEl.value = this._editorEl.innerHTML;
2155
+ this._editorEl.style.display = "none";
2156
+ this._sourceEl.style.display = "";
2157
+ }
2158
+ } else {
2159
+ if (this._sourceEl && this._editorEl) {
2160
+ this._editorEl.innerHTML = this.sanitize(this._sourceEl.value);
2161
+ this._sourceEl.style.display = "none";
2162
+ this._editorEl.style.display = "";
2163
+ }
2164
+ }
2165
+ }
2166
+ isSourceEdit() {
2167
+ return this._sourceMode;
2168
+ }
2169
+ // -----------------------------------------------------------------------
2170
+ // Sanitize
2171
+ // -----------------------------------------------------------------------
2172
+ sanitize(html) {
2173
+ return html.replace(XSS_TAGS, "");
2174
+ }
2175
+ };
2176
+
2177
+ // src/field/Spinner.ts
2178
+ var Spinner = class extends TextField {
2179
+ static $className = "Ext.form.field.Spinner";
2180
+ constructor(config = {}) {
2181
+ const triggers = [
2182
+ { type: "spinner-up", handler: () => this.spinUp() },
2183
+ { type: "spinner-down", handler: () => this.spinDown() },
2184
+ ...config.triggers ?? []
2185
+ ];
2186
+ super({ xtype: "spinnerfield", ...config, triggers });
2187
+ }
2188
+ afterRender() {
2189
+ super.afterRender();
2190
+ this.el.classList.add("x-spinner");
2191
+ this.getInputEl().addEventListener("keydown", (e) => {
2192
+ if (e.key === "ArrowUp") {
2193
+ e.preventDefault();
2194
+ this.spinUp();
2195
+ } else if (e.key === "ArrowDown") {
2196
+ e.preventDefault();
2197
+ this.spinDown();
2198
+ }
2199
+ });
2200
+ }
2201
+ spinUp() {
2202
+ this.onSpinUp();
2203
+ this.fire("spin", this, "up");
2204
+ }
2205
+ spinDown() {
2206
+ this.onSpinDown();
2207
+ this.fire("spin", this, "down");
2208
+ }
2209
+ };
2210
+ export {
2211
+ BasicForm,
2212
+ BoundList,
2213
+ Checkbox,
2214
+ CheckboxGroup,
2215
+ ColorPicker,
2216
+ ComboBox,
2217
+ DateField,
2218
+ DatePicker,
2219
+ DisplayField,
2220
+ Field,
2221
+ FieldContainer,
2222
+ FileField,
2223
+ FormPanel,
2224
+ HiddenField,
2225
+ HtmlEditor,
2226
+ NumberField,
2227
+ Radio,
2228
+ RadioGroup,
2229
+ Slider,
2230
+ Spinner,
2231
+ TagField,
2232
+ TextArea,
2233
+ TextField,
2234
+ VTypes,
2235
+ addDate,
2236
+ alpha,
2237
+ alphanum,
2238
+ custom,
2239
+ diffDate,
2240
+ email,
2241
+ exclusion,
2242
+ format,
2243
+ formatDate,
2244
+ getDaysInMonth,
2245
+ getMonthName,
2246
+ getMonthShortName,
2247
+ inclusion,
2248
+ isLeapYear,
2249
+ length,
2250
+ parseDate,
2251
+ presence,
2252
+ range,
2253
+ url,
2254
+ validateChain
2255
+ };
2256
+ //# sourceMappingURL=index.js.map