kmaterialize 2.3.3-1.0.2 → 2.3.3-1.0.21

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.
Files changed (42) hide show
  1. package/README.md +4 -4
  2. package/components/air-datepicker/_air-datepicker.scss +137 -0
  3. package/components/air-datepicker/air-datepicker.ts +192 -0
  4. package/components/alert/_alerts.scss +130 -0
  5. package/components/alert/alert.ts +73 -0
  6. package/components/appbar/appbar.css +301 -4
  7. package/components/button/_buttons.scss +39 -1
  8. package/components/card/_cards.scss +9 -1
  9. package/components/checkbox/_checkboxes.scss +120 -229
  10. package/components/collapsible/_collapsible.scss +50 -0
  11. package/components/color-input/_color-input.scss +124 -0
  12. package/components/color-input/color-input.ts +203 -0
  13. package/components/dropdown/dropdown.ts +1 -1
  14. package/components/file-input/_file-input-enhanced.scss +61 -0
  15. package/components/file-input/file-input.ts +203 -0
  16. package/components/kanban/_kanban.scss +148 -0
  17. package/components/kanban/kanban.ts +133 -0
  18. package/components/list/_collection.scss +10 -2
  19. package/components/number-input/number-input.ts +103 -0
  20. package/components/password-input/password-input.ts +72 -0
  21. package/components/slider/_range.scss +5 -1
  22. package/components/switch/_switches.scss +5 -0
  23. package/components/textfield/_input-fields.scss +4 -4
  24. package/components/textfield/_select.scss +54 -0
  25. package/components/tom-select/_tom-select-field.scss +256 -0
  26. package/components/tom-select/tom-select-field.ts +175 -0
  27. package/components/toolbar/_toolbar.scss +324 -0
  28. package/components/toolbar/toolbar.ts +152 -0
  29. package/dist/css/materialize.colors.min.css +2 -2
  30. package/dist/css/materialize.css +10575 -711
  31. package/dist/css/materialize.min.css +2 -2
  32. package/dist/css/materialize.min.css.map +1 -1
  33. package/dist/js/materialize.cjs.js +1136 -45
  34. package/dist/js/materialize.d.ts +227 -2
  35. package/dist/js/materialize.js +1136 -45
  36. package/dist/js/materialize.min.js +2 -2
  37. package/dist/js/materialize.mjs +1128 -46
  38. package/package.json +45 -1
  39. package/sass/_colors.scss +191 -5
  40. package/sass/_global.scss +240 -1
  41. package/sass/materialize.scss +14 -1
  42. package/sass/mixins.module.scss +6 -6
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Materialize v2.3.3-1.0.2 (https://materializeweb.com)
2
+ * Materialize v2.3.3-1.0.21 (https://materializeweb.com)
3
3
  * Copyright 2014-2026 Materialize
4
4
  * MIT License (https://raw.githubusercontent.com/materializecss/materialize/master/LICENSE)
5
5
  */
@@ -397,7 +397,7 @@ class Component {
397
397
  }
398
398
  }
399
399
 
400
- const _defaults$o = {
400
+ const _defaults$x = {
401
401
  alignment: 'left',
402
402
  autoFocus: true,
403
403
  constrainWidth: true,
@@ -450,7 +450,7 @@ class Dropdown extends Component {
450
450
  this._setupEventHandlers();
451
451
  }
452
452
  static get defaults() {
453
- return _defaults$o;
453
+ return _defaults$x;
454
454
  }
455
455
  /**
456
456
  * Initializes instances of Dropdown.
@@ -542,7 +542,7 @@ class Dropdown extends Component {
542
542
  // isTouchMoving to check if scrolling on mobile.
543
543
  this.close();
544
544
  }
545
- else if (!target.closest('.dropdown-content')) {
545
+ else if (!target.closest('.dropdown-content') && !target.closest('.dropdown-trigger')) {
546
546
  // Do this one frame later so that if the element clicked also triggers _handleClick
547
547
  // For example, if a label for a select was clicked, that we don't close/open the dropdown
548
548
  setTimeout(() => {
@@ -901,7 +901,7 @@ class Dropdown extends Component {
901
901
  };
902
902
  }
903
903
 
904
- const _defaults$n = {
904
+ const _defaults$w = {
905
905
  data: [], // Autocomplete data set
906
906
  onAutocomplete: null, // Callback for when autocompleted
907
907
  dropdownOptions: {
@@ -961,7 +961,7 @@ class Autocomplete extends Component {
961
961
  this._setupEventHandlers();
962
962
  }
963
963
  static get defaults() {
964
- return _defaults$n;
964
+ return _defaults$w;
965
965
  }
966
966
  /**
967
967
  * Initializes instances of Autocomplete.
@@ -1369,7 +1369,177 @@ class Autocomplete extends Component {
1369
1369
  }
1370
1370
  }
1371
1371
 
1372
- const _defaults$m = {
1372
+ const _defaults$v = {
1373
+ dismissible: true
1374
+ };
1375
+ /** A persistent, contextual feedback banner. */
1376
+ class Alert extends Component {
1377
+ _closeButton = null;
1378
+ _onClose = () => this.dismiss();
1379
+ constructor(el, options) {
1380
+ super(el, options, Alert);
1381
+ this.options = { ...Alert.defaults, ...options };
1382
+ this.el['M_Alert'] = this;
1383
+ this._bindCloseButton();
1384
+ }
1385
+ static get defaults() {
1386
+ return _defaults$v;
1387
+ }
1388
+ static init(els, options = {}) {
1389
+ return super.init(els, options, Alert);
1390
+ }
1391
+ static getInstance(el) {
1392
+ return el['M_Alert'];
1393
+ }
1394
+ _bindCloseButton() {
1395
+ this._closeButton = this.el.querySelector('.alert-close');
1396
+ if (this.options.dismissible && this._closeButton) {
1397
+ this._closeButton.addEventListener('click', this._onClose);
1398
+ }
1399
+ }
1400
+ /** Hide and remove the alert from the document. */
1401
+ dismiss() {
1402
+ if (!this.el.isConnected)
1403
+ return;
1404
+ this.el.classList.add('alert-dismissing');
1405
+ const remove = () => {
1406
+ this.el.removeEventListener('transitionend', remove);
1407
+ this.el.hidden = true;
1408
+ this.options.onDismiss?.(this);
1409
+ };
1410
+ if (getComputedStyle(this.el).transitionDuration === '0s')
1411
+ remove();
1412
+ else
1413
+ this.el.addEventListener('transitionend', remove, { once: true });
1414
+ }
1415
+ /** Show an alert that was previously dismissed. */
1416
+ open() {
1417
+ this.el.hidden = false;
1418
+ this.el.classList.remove('alert-dismissing');
1419
+ }
1420
+ destroy() {
1421
+ this._closeButton?.removeEventListener('click', this._onClose);
1422
+ delete this.el['M_Alert'];
1423
+ }
1424
+ }
1425
+
1426
+ const _defaults$u = {
1427
+ draggable: true
1428
+ };
1429
+ /** A lightweight, dependency-free board for columns of draggable cards. */
1430
+ class Kanban extends Component {
1431
+ _draggedCard = null;
1432
+ _dragSourceColumn = null;
1433
+ _onDragStart = (event) => {
1434
+ const card = event.target?.closest('.kanban-card');
1435
+ if (!card || !this.options.draggable)
1436
+ return;
1437
+ this._draggedCard = card;
1438
+ this._dragSourceColumn = card.closest('.kanban-column');
1439
+ card.classList.add('is-dragging');
1440
+ event.dataTransfer?.setData('text/plain', card.dataset.kanbanCard || '');
1441
+ if (event.dataTransfer)
1442
+ event.dataTransfer.effectAllowed = 'move';
1443
+ };
1444
+ _onDragOver = (event) => {
1445
+ if (!this._draggedCard)
1446
+ return;
1447
+ const column = event.target?.closest('.kanban-column');
1448
+ if (!column || !this.el.contains(column))
1449
+ return;
1450
+ event.preventDefault();
1451
+ if (event.dataTransfer)
1452
+ event.dataTransfer.dropEffect = 'move';
1453
+ this.el.querySelectorAll('.kanban-column.is-drag-over').forEach((item) => item.classList.remove('is-drag-over'));
1454
+ column.classList.add('is-drag-over');
1455
+ };
1456
+ _onDrop = (event) => {
1457
+ if (!this._draggedCard)
1458
+ return;
1459
+ const column = event.target?.closest('.kanban-column');
1460
+ if (!column || !this.el.contains(column))
1461
+ return;
1462
+ event.preventDefault();
1463
+ const body = column.querySelector('.kanban-column-body') || column;
1464
+ const targetCard = event.target?.closest('.kanban-card');
1465
+ const source = this._dragSourceColumn;
1466
+ if (targetCard && targetCard !== this._draggedCard && targetCard.parentElement === body) {
1467
+ const box = targetCard.getBoundingClientRect();
1468
+ body.insertBefore(this._draggedCard, event.clientY < box.top + box.height / 2 ? targetCard : targetCard.nextSibling);
1469
+ }
1470
+ else {
1471
+ body.appendChild(this._draggedCard);
1472
+ }
1473
+ this._clearDragState();
1474
+ this._updateCounts();
1475
+ if (source && source !== column)
1476
+ this.options.onMove?.({ card: this._draggedCard, from: source, to: column });
1477
+ };
1478
+ _onDragEnd = () => this._clearDragState();
1479
+ constructor(el, options) {
1480
+ super(el, options, Kanban);
1481
+ this.options = { ...Kanban.defaults, ...options };
1482
+ this.el['M_Kanban'] = this;
1483
+ this._prepareMarkup();
1484
+ if (this.options.draggable)
1485
+ this._bindEvents();
1486
+ }
1487
+ static get defaults() {
1488
+ return _defaults$u;
1489
+ }
1490
+ static init(els, options = {}) {
1491
+ return super.init(els, options, Kanban);
1492
+ }
1493
+ static getInstance(el) {
1494
+ return el['M_Kanban'];
1495
+ }
1496
+ _prepareMarkup() {
1497
+ this.el.setAttribute('role', 'region');
1498
+ this.el.querySelectorAll('.kanban-column').forEach((column) => {
1499
+ column.setAttribute('role', 'group');
1500
+ const body = column.querySelector('.kanban-column-body');
1501
+ if (body)
1502
+ body.setAttribute('role', 'list');
1503
+ column.querySelectorAll('.kanban-card').forEach((card) => {
1504
+ card.setAttribute('role', 'listitem');
1505
+ card.setAttribute('tabindex', '0');
1506
+ if (this.options.draggable)
1507
+ card.draggable = true;
1508
+ });
1509
+ });
1510
+ this._updateCounts();
1511
+ }
1512
+ _bindEvents() {
1513
+ this.el.addEventListener('dragstart', this._onDragStart);
1514
+ this.el.addEventListener('dragover', this._onDragOver);
1515
+ this.el.addEventListener('drop', this._onDrop);
1516
+ this.el.addEventListener('dragend', this._onDragEnd);
1517
+ }
1518
+ _clearDragState() {
1519
+ this._draggedCard?.classList.remove('is-dragging');
1520
+ this.el.querySelectorAll('.kanban-column.is-drag-over').forEach((item) => item.classList.remove('is-drag-over'));
1521
+ this._draggedCard = null;
1522
+ this._dragSourceColumn = null;
1523
+ }
1524
+ _updateCounts() {
1525
+ this.el.querySelectorAll('.kanban-column').forEach((column) => {
1526
+ const count = column.querySelector('.kanban-column-count');
1527
+ const body = column.querySelector('.kanban-column-body');
1528
+ if (count && body)
1529
+ count.textContent = String(body.querySelectorAll(':scope > .kanban-card').length);
1530
+ });
1531
+ }
1532
+ destroy() {
1533
+ this._clearDragState();
1534
+ this.el.removeEventListener('dragstart', this._onDragStart);
1535
+ this.el.removeEventListener('dragover', this._onDragOver);
1536
+ this.el.removeEventListener('drop', this._onDrop);
1537
+ this.el.removeEventListener('dragend', this._onDragEnd);
1538
+ delete this.el['M_Kanban'];
1539
+ }
1540
+ }
1541
+
1542
+ const _defaults$t = {
1373
1543
  direction: 'top',
1374
1544
  hoverEnabled: true,
1375
1545
  toolbarEnabled: false
@@ -1416,7 +1586,7 @@ class FloatingActionButton extends Component {
1416
1586
  this.#setupEventHandlers();
1417
1587
  }
1418
1588
  static get defaults() {
1419
- return _defaults$m;
1589
+ return _defaults$t;
1420
1590
  }
1421
1591
  /**
1422
1592
  * Initializes instances of FloatingActionButton.
@@ -1587,7 +1757,7 @@ class FloatingActionButton extends Component {
1587
1757
  }
1588
1758
  }
1589
1759
 
1590
- const _defaults$l = {
1760
+ const _defaults$s = {
1591
1761
  onOpen: null,
1592
1762
  onClose: null,
1593
1763
  inDuration: 225,
@@ -1623,7 +1793,7 @@ class Cards extends Component {
1623
1793
  }
1624
1794
  }
1625
1795
  static get defaults() {
1626
- return _defaults$l;
1796
+ return _defaults$s;
1627
1797
  }
1628
1798
  /**
1629
1799
  * Initializes instances of Cards.
@@ -1734,7 +1904,7 @@ class Cards extends Component {
1734
1904
  }
1735
1905
  }
1736
1906
 
1737
- const _defaults$k = {
1907
+ const _defaults$r = {
1738
1908
  duration: 200, // ms
1739
1909
  dist: -100, // zoom scale TODO: make this more intuitive as an option
1740
1910
  shift: 0, // spacing for center image
@@ -1836,7 +2006,7 @@ class Carousel extends Component {
1836
2006
  this._scroll(this.offset);
1837
2007
  }
1838
2008
  static get defaults() {
1839
- return _defaults$k;
2009
+ return _defaults$r;
1840
2010
  }
1841
2011
  /**
1842
2012
  * Initializes instances of Carousel.
@@ -2308,7 +2478,7 @@ class Carousel extends Component {
2308
2478
  }
2309
2479
 
2310
2480
  var _a;
2311
- const _defaults$j = {
2481
+ const _defaults$q = {
2312
2482
  data: [],
2313
2483
  placeholder: '',
2314
2484
  secondaryPlaceholder: '',
@@ -2362,7 +2532,7 @@ class Chips extends Component {
2362
2532
  }
2363
2533
  }
2364
2534
  static get defaults() {
2365
- return _defaults$j;
2535
+ return _defaults$q;
2366
2536
  }
2367
2537
  /**
2368
2538
  * Initializes instances of Chips.
@@ -2645,7 +2815,7 @@ class Chips extends Component {
2645
2815
  }
2646
2816
  _a = Chips;
2647
2817
 
2648
- const _defaults$i = {
2818
+ const _defaults$p = {
2649
2819
  accordion: true,
2650
2820
  onOpenStart: null,
2651
2821
  onOpenEnd: null,
@@ -2681,7 +2851,7 @@ class Collapsible extends Component {
2681
2851
  }
2682
2852
  }
2683
2853
  static get defaults() {
2684
- return _defaults$i;
2854
+ return _defaults$p;
2685
2855
  }
2686
2856
  /**
2687
2857
  * Initializes instances of Collapsible.
@@ -2800,7 +2970,7 @@ class Collapsible extends Component {
2800
2970
  };
2801
2971
  }
2802
2972
 
2803
- const _defaults$h = {
2973
+ const _defaults$o = {
2804
2974
  classes: '',
2805
2975
  dropdownOptions: {}
2806
2976
  };
@@ -2840,7 +3010,7 @@ class FormSelect extends Component {
2840
3010
  this._setupEventHandlers();
2841
3011
  }
2842
3012
  static get defaults() {
2843
- return _defaults$h;
3013
+ return _defaults$o;
2844
3014
  }
2845
3015
  /**
2846
3016
  * Initializes instances of FormSelect.
@@ -3183,7 +3353,7 @@ class FormSelect extends Component {
3183
3353
  }
3184
3354
  }
3185
3355
 
3186
- const _defaults$g = {
3356
+ const _defaults$n = {
3187
3357
  margin: 5,
3188
3358
  transition: 10,
3189
3359
  duration: 250,
@@ -3200,7 +3370,7 @@ class DockedDisplayPlugin {
3200
3370
  constructor(el, container, options) {
3201
3371
  this.el = el;
3202
3372
  this.options = {
3203
- ..._defaults$g,
3373
+ ..._defaults$n,
3204
3374
  ...options
3205
3375
  };
3206
3376
  this.container = document.createElement('div');
@@ -3262,7 +3432,7 @@ class DockedDisplayPlugin {
3262
3432
  };
3263
3433
  }
3264
3434
 
3265
- const _defaults$f = {
3435
+ const _defaults$m = {
3266
3436
  classList: ['modal'],
3267
3437
  title: null,
3268
3438
  onOpen: null,
@@ -3277,7 +3447,7 @@ class ModalDisplayPlugin {
3277
3447
  constructor(el, container, options) {
3278
3448
  this.el = el;
3279
3449
  this.options = {
3280
- ..._defaults$f,
3450
+ ..._defaults$m,
3281
3451
  ...options,
3282
3452
  };
3283
3453
  this.container = document.createElement('dialog');
@@ -3335,7 +3505,7 @@ class ModalDisplayPlugin {
3335
3505
  };
3336
3506
  }
3337
3507
 
3338
- const _defaults$e = {
3508
+ const _defaults$l = {
3339
3509
  // the default output format for the input field value
3340
3510
  format: 'mmm dd, yyyy',
3341
3511
  // Used to create date object from current input string
@@ -3485,7 +3655,7 @@ class Datepicker extends Component {
3485
3655
  this._pickerSetup();
3486
3656
  }
3487
3657
  static get defaults() {
3488
- return _defaults$e;
3658
+ return _defaults$l;
3489
3659
  }
3490
3660
  /**
3491
3661
  * Initializes instances of Datepicker.
@@ -4538,7 +4708,7 @@ class Forms {
4538
4708
  }
4539
4709
  }
4540
4710
 
4541
- const _defaults$d = {
4711
+ const _defaults$k = {
4542
4712
  inDuration: 275,
4543
4713
  outDuration: 200,
4544
4714
  onOpenStart: null,
@@ -4590,7 +4760,7 @@ class Materialbox extends Component {
4590
4760
  this._setupEventHandlers();
4591
4761
  }
4592
4762
  static get defaults() {
4593
- return _defaults$d;
4763
+ return _defaults$k;
4594
4764
  }
4595
4765
  /**
4596
4766
  * Initializes instances of MaterialBox.
@@ -4914,7 +5084,7 @@ class Materialbox extends Component {
4914
5084
  };
4915
5085
  }
4916
5086
 
4917
- const _defaults$c = {
5087
+ const _defaults$j = {
4918
5088
  opacity: 0.5,
4919
5089
  inDuration: 250,
4920
5090
  outDuration: 250,
@@ -4939,7 +5109,7 @@ class Modal extends Component {
4939
5109
  this._setupEventHandlers();
4940
5110
  }
4941
5111
  static get defaults() {
4942
- return _defaults$c;
5112
+ return _defaults$j;
4943
5113
  }
4944
5114
  static init(els, options = {}) {
4945
5115
  return super.init(els, options, Modal);
@@ -4982,7 +5152,7 @@ class Modal extends Component {
4982
5152
  static { }
4983
5153
  }
4984
5154
 
4985
- const _defaults$b = {
5155
+ const _defaults$i = {
4986
5156
  edge: 'left',
4987
5157
  draggable: true,
4988
5158
  dragTargetWidth: '10px',
@@ -5038,7 +5208,7 @@ class Sidenav extends Component {
5038
5208
  Sidenav._sidenavs.push(this);
5039
5209
  }
5040
5210
  static get defaults() {
5041
- return _defaults$b;
5211
+ return _defaults$i;
5042
5212
  }
5043
5213
  /**
5044
5214
  * Initializes instances of Sidenav.
@@ -5435,7 +5605,7 @@ class Sidenav extends Component {
5435
5605
  }
5436
5606
  }
5437
5607
 
5438
- const _defaults$a = {
5608
+ const _defaults$h = {
5439
5609
  indicators: true,
5440
5610
  height: 400,
5441
5611
  duration: 500,
@@ -5527,7 +5697,7 @@ class Slider extends Component {
5527
5697
  this.start();
5528
5698
  }
5529
5699
  static get defaults() {
5530
- return _defaults$a;
5700
+ return _defaults$h;
5531
5701
  }
5532
5702
  /**
5533
5703
  * Initializes instances of Slider.
@@ -5774,7 +5944,7 @@ class Slider extends Component {
5774
5944
  };
5775
5945
  }
5776
5946
 
5777
- const _defaults$9 = {
5947
+ const _defaults$g = {
5778
5948
  duration: 300,
5779
5949
  onShow: null,
5780
5950
  swipeable: false,
@@ -5811,7 +5981,7 @@ class Tabs extends Component {
5811
5981
  this._setupEventHandlers();
5812
5982
  }
5813
5983
  static get defaults() {
5814
- return _defaults$9;
5984
+ return _defaults$g;
5815
5985
  }
5816
5986
  /**
5817
5987
  * Initializes instances of Tabs.
@@ -6062,7 +6232,7 @@ class Tabs extends Component {
6062
6232
  }
6063
6233
  }
6064
6234
 
6065
- const _defaults$8 = {
6235
+ const _defaults$f = {
6066
6236
  dialRadius: 135,
6067
6237
  outerRadius: 105,
6068
6238
  innerRadius: 70,
@@ -6149,7 +6319,7 @@ class Timepicker extends Component {
6149
6319
  this._pickerSetup();
6150
6320
  }
6151
6321
  static get defaults() {
6152
- return _defaults$8;
6322
+ return _defaults$f;
6153
6323
  }
6154
6324
  /**
6155
6325
  * Initializes instances of Timepicker.
@@ -6755,7 +6925,7 @@ class Timepicker extends Component {
6755
6925
  }
6756
6926
  }
6757
6927
 
6758
- const _defaults$7 = {
6928
+ const _defaults$e = {
6759
6929
  text: '',
6760
6930
  displayLength: 4000,
6761
6931
  inDuration: 300,
@@ -6808,7 +6978,7 @@ class Toast {
6808
6978
  this._setTimer();
6809
6979
  }
6810
6980
  static get defaults() {
6811
- return _defaults$7;
6981
+ return _defaults$e;
6812
6982
  }
6813
6983
  static getInstance(el) {
6814
6984
  return el['M_Toast'];
@@ -7000,7 +7170,7 @@ class Toast {
7000
7170
  }
7001
7171
  }
7002
7172
 
7003
- const _defaults$6 = {
7173
+ const _defaults$d = {
7004
7174
  exitDelay: 200,
7005
7175
  enterDelay: 0,
7006
7176
  text: '',
@@ -7044,7 +7214,7 @@ class Tooltip extends Component {
7044
7214
  this._setupEventHandlers();
7045
7215
  }
7046
7216
  static get defaults() {
7047
- return _defaults$6;
7217
+ return _defaults$d;
7048
7218
  }
7049
7219
  /**
7050
7220
  * Initializes instances of Tooltip.
@@ -7269,7 +7439,7 @@ class Tooltip extends Component {
7269
7439
  }
7270
7440
  }
7271
7441
 
7272
- const _defaults$5 = {};
7442
+ const _defaults$c = {};
7273
7443
  // TODO: !!!!!
7274
7444
  class Range extends Component {
7275
7445
  _mousedown;
@@ -7287,7 +7457,7 @@ class Range extends Component {
7287
7457
  this._setupEventHandlers();
7288
7458
  }
7289
7459
  static get defaults() {
7290
- return _defaults$5;
7460
+ return _defaults$c;
7291
7461
  }
7292
7462
  /**
7293
7463
  * Initializes instances of Range.
@@ -7438,6 +7608,896 @@ class Range extends Component {
7438
7608
  }
7439
7609
  }
7440
7610
 
7611
+ const _defaults$b = {};
7612
+ // @implement /Users/kzarshenas/Sites/RodeoFx/rodeo_toolkit_2/app/Environment/Partials/PlanningToolbar.ts
7613
+ // Generic version of that file's track-indicator sliding and expandable
7614
+ // search behavior, decoupled from the page-specific filter/state-binding
7615
+ // system (UtilityFilter) the source used - here it's just plain DOM state.
7616
+ class Toolbar extends Component {
7617
+ _tracks;
7618
+ constructor(el, options) {
7619
+ super(el, options, Toolbar);
7620
+ this.el.M_Toolbar = this;
7621
+ this.options = {
7622
+ ...Toolbar.defaults,
7623
+ ...options
7624
+ };
7625
+ this._tracks = Array.from(this.el.querySelectorAll('.toolbar-track'));
7626
+ this._setupEventHandlers();
7627
+ this._tracks.forEach((track) => this._moveIndicator(track, false));
7628
+ // A custom/web font can still be loading at construction time; its
7629
+ // metrics landing after this first measurement would leave the
7630
+ // indicator very slightly offset from the (now differently-sized)
7631
+ // text it's supposed to sit behind. Re-measure once fonts are ready.
7632
+ if (typeof document !== 'undefined' && document.fonts) {
7633
+ document.fonts.ready.then(() => {
7634
+ this._tracks.forEach((track) => this._moveIndicator(track, false));
7635
+ });
7636
+ }
7637
+ }
7638
+ static get defaults() {
7639
+ return _defaults$b;
7640
+ }
7641
+ static init(els, options = {}) {
7642
+ return super.init(els, options, Toolbar);
7643
+ }
7644
+ static getInstance(el) {
7645
+ return el.M_Toolbar;
7646
+ }
7647
+ destroy() {
7648
+ this._removeEventHandlers();
7649
+ this.el.M_Toolbar = undefined;
7650
+ }
7651
+ /**
7652
+ * Re-measure and reposition every track's sliding indicator - call this
7653
+ * after changing which .toolbar-track-item is active from your own code,
7654
+ * or after anything that could have changed the toolbar's layout/width.
7655
+ */
7656
+ updateIndicators() {
7657
+ this._tracks.forEach((track) => this._moveIndicator(track, true));
7658
+ }
7659
+ _setupEventHandlers() {
7660
+ this.el.addEventListener('click', this._handleTrackClick);
7661
+ this.el.addEventListener('focusin', this._handleSearchFocusIn);
7662
+ this.el.addEventListener('focusout', this._handleSearchFocusOut);
7663
+ this.el.addEventListener('input', this._handleSearchInput);
7664
+ this.el.addEventListener('keydown', this._handleSearchKeydown);
7665
+ window.addEventListener('resize', this._handleWindowResize);
7666
+ }
7667
+ _removeEventHandlers() {
7668
+ this.el.removeEventListener('click', this._handleTrackClick);
7669
+ this.el.removeEventListener('focusin', this._handleSearchFocusIn);
7670
+ this.el.removeEventListener('focusout', this._handleSearchFocusOut);
7671
+ this.el.removeEventListener('input', this._handleSearchInput);
7672
+ this.el.removeEventListener('keydown', this._handleSearchKeydown);
7673
+ window.removeEventListener('resize', this._handleWindowResize);
7674
+ }
7675
+ _handleTrackClick = (e) => {
7676
+ const item = e.target.closest('.toolbar-track-item');
7677
+ if (!item)
7678
+ return;
7679
+ const track = item.closest('.toolbar-track');
7680
+ if (!track || !this._tracks.includes(track))
7681
+ return;
7682
+ track.querySelectorAll('.toolbar-track-item.is-active').forEach((el) => el.classList.remove('is-active'));
7683
+ item.classList.add('is-active');
7684
+ this._moveIndicator(track, true);
7685
+ };
7686
+ _handleSearchFocusIn = (e) => {
7687
+ const search = e.target.closest('.toolbar-search');
7688
+ if (search)
7689
+ search.classList.add('is-expanded');
7690
+ };
7691
+ _handleSearchFocusOut = (e) => {
7692
+ const search = e.target.closest('.toolbar-search');
7693
+ if (!search)
7694
+ return;
7695
+ const input = search.querySelector('.toolbar-search-input');
7696
+ if (input && input.value === '')
7697
+ search.classList.remove('is-expanded');
7698
+ };
7699
+ _handleSearchInput = (e) => {
7700
+ const input = e.target;
7701
+ if (!input.classList.contains('toolbar-search-input'))
7702
+ return;
7703
+ const search = input.closest('.toolbar-search');
7704
+ if (search)
7705
+ search.classList.toggle('is-expanded', input.value !== '' || document.activeElement === input);
7706
+ };
7707
+ _handleSearchKeydown = (e) => {
7708
+ if (e.key !== 'Escape')
7709
+ return;
7710
+ const input = e.target.closest('.toolbar-search')?.querySelector('.toolbar-search-input');
7711
+ if (!input)
7712
+ return;
7713
+ input.value = '';
7714
+ input.blur();
7715
+ };
7716
+ _handleWindowResize = () => {
7717
+ this._tracks.forEach((track) => this._moveIndicator(track, false));
7718
+ };
7719
+ _moveIndicator(track, animate) {
7720
+ const indicator = track.querySelector('.toolbar-track-indicator');
7721
+ const active = track.querySelector('.toolbar-track-item.is-active');
7722
+ if (!indicator || !active)
7723
+ return;
7724
+ if (!animate)
7725
+ indicator.style.transition = 'none';
7726
+ // No "- track.clientLeft" here: offsetLeft is already relative to the
7727
+ // offsetParent's padding edge, same origin as the indicator's own
7728
+ // `left: 0` (an absolutely positioned element's containing block is
7729
+ // its ancestor's padding box) - subtracting the border width again
7730
+ // was double-counting it, nudging the indicator a px too far left.
7731
+ indicator.style.transform = `translateX(${active.offsetLeft}px)`;
7732
+ indicator.style.width = `${active.offsetWidth}px`;
7733
+ track.classList.add('is-armed');
7734
+ if (!animate) {
7735
+ // Force layout so the transition-less move above actually applies
7736
+ // before re-enabling transitions on the next frame.
7737
+ void indicator.offsetWidth;
7738
+ requestAnimationFrame(() => {
7739
+ indicator.style.transition = '';
7740
+ });
7741
+ }
7742
+ }
7743
+ }
7744
+
7745
+ const _defaults$a = {};
7746
+ // @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Password.ts
7747
+ // Show/hide toggle for a password input - no third-party dependency, just
7748
+ // Materialize's own .prefix/.suffix icon-slot convention (see
7749
+ // components/textfield/_input-fields.scss), so unlike the other new form
7750
+ // enhancements in this batch this one needs no peer-loader/dynamic import.
7751
+ class PasswordInput extends Component {
7752
+ _suffixEl;
7753
+ constructor(el, options) {
7754
+ super(el, options, PasswordInput);
7755
+ this.el.M_PasswordInput = this;
7756
+ this.options = {
7757
+ ...PasswordInput.defaults,
7758
+ ...options
7759
+ };
7760
+ this._suffixEl = this.el.parentElement?.querySelector('[data-password-toggle-icon]') ?? null;
7761
+ if (!this.el.dataset.passwordVisible)
7762
+ this.el.dataset.passwordVisible = '0';
7763
+ this._setupEventHandlers();
7764
+ }
7765
+ static get defaults() {
7766
+ return _defaults$a;
7767
+ }
7768
+ static init(els, options = {}) {
7769
+ return super.init(els, options, PasswordInput);
7770
+ }
7771
+ static getInstance(el) {
7772
+ return el.M_PasswordInput;
7773
+ }
7774
+ destroy() {
7775
+ this._removeEventHandlers();
7776
+ this.el.M_PasswordInput = undefined;
7777
+ }
7778
+ _setupEventHandlers() {
7779
+ this._suffixEl?.addEventListener('click', this._handleToggleClick);
7780
+ }
7781
+ _removeEventHandlers() {
7782
+ this._suffixEl?.removeEventListener('click', this._handleToggleClick);
7783
+ }
7784
+ _handleToggleClick = () => {
7785
+ const visible = this.el.dataset.passwordVisible === '1';
7786
+ this.el.type = visible ? 'password' : 'text';
7787
+ this.el.dataset.passwordVisible = visible ? '0' : '1';
7788
+ const iconEl = this._suffixEl?.querySelector('i');
7789
+ if (iconEl)
7790
+ iconEl.textContent = visible ? 'visibility' : 'visibility_off';
7791
+ };
7792
+ }
7793
+
7794
+ const _cache = new Map();
7795
+ async function loadPeer(spec, importer) {
7796
+ if (_cache.has(spec.specifier))
7797
+ return _cache.get(spec.specifier);
7798
+ try {
7799
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7800
+ const mod = await importer();
7801
+ const resolved = mod?.default ?? mod;
7802
+ _cache.set(spec.specifier, resolved);
7803
+ return resolved;
7804
+ }
7805
+ catch {
7806
+ // fall through to the global-scope lookup below
7807
+ }
7808
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7809
+ const globalScope = typeof window !== 'undefined' ? window : undefined;
7810
+ if (globalScope && globalScope[spec.globalName]) {
7811
+ const resolved = globalScope[spec.globalName];
7812
+ _cache.set(spec.specifier, resolved);
7813
+ return resolved;
7814
+ }
7815
+ throw new Error(`kmaterialize's ${spec.feature} requires "${spec.specifier}", which isn't installed/loaded.\n` +
7816
+ `- If you're using a bundler: npm/pnpm/yarn install "${spec.specifier}".\n` +
7817
+ `- If you're using the plain <script> (no-bundler) build: add\n` +
7818
+ ` ${spec.cdnHint}\n` +
7819
+ ` before initializing this component, so window.${spec.globalName} is defined.`);
7820
+ }
7821
+
7822
+ const _defaults$9 = {
7823
+ thousandsSeparator: ' ',
7824
+ radix: '.',
7825
+ mapToRadix: [',']
7826
+ };
7827
+ // @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Number.ts
7828
+ // Masked/formatted numeric input via IMask (thousands separator, decimal
7829
+ // scale, min/max) - deliberately opts in on `type="text" data-type="number"`
7830
+ // rather than `type="number"`, so IMask can format the display value
7831
+ // (thousands separators etc.) that a native number input wouldn't allow.
7832
+ //
7833
+ // IMask is an optional peerDependency, loaded on demand via peer-loader -
7834
+ // see that file for the bundler/no-bundler resolution strategy.
7835
+ class NumberInput extends Component {
7836
+ mask;
7837
+ ready;
7838
+ constructor(el, options) {
7839
+ super(el, options, NumberInput);
7840
+ this.el.M_NumberInput = this;
7841
+ this.options = {
7842
+ ...NumberInput.defaults,
7843
+ ...options
7844
+ };
7845
+ this.ready = this._setup();
7846
+ }
7847
+ static get defaults() {
7848
+ return _defaults$9;
7849
+ }
7850
+ static init(els, options = {}) {
7851
+ return super.init(els, options, NumberInput);
7852
+ }
7853
+ static getInstance(el) {
7854
+ return el.M_NumberInput;
7855
+ }
7856
+ destroy() {
7857
+ this.mask?.destroy();
7858
+ this.el.M_NumberInput = undefined;
7859
+ }
7860
+ async _setup() {
7861
+ const IMask = await loadPeer({
7862
+ specifier: 'imask',
7863
+ globalName: 'IMask',
7864
+ feature: 'Number input (IMask) enhancement',
7865
+ cdnHint: '<script src="path/to/imask.min.js"></script> (self-hosted - copy from node_modules/imask/dist/imask.min.js, or a CDN of your choice)'
7866
+ }, () => import('imask'));
7867
+ const maskOptions = {
7868
+ mask: Number,
7869
+ skipInvalid: true,
7870
+ thousandsSeparator: this.options.thousandsSeparator,
7871
+ radix: this.options.radix,
7872
+ mapToRadix: this.options.mapToRadix,
7873
+ autofix: true
7874
+ };
7875
+ const max = this.options.max ?? (this.el.hasAttribute('max') ? Number(this.el.getAttribute('max')) : undefined);
7876
+ if (max !== undefined && !Number.isNaN(max))
7877
+ maskOptions.max = max;
7878
+ const min = this.options.min ?? (this.el.hasAttribute('min') ? Number(this.el.getAttribute('min')) : undefined);
7879
+ if (min !== undefined && !Number.isNaN(min))
7880
+ maskOptions.min = min;
7881
+ const step = this.el.getAttribute('step');
7882
+ const scale = this.options.scale ?? (step?.includes('.') ? step.split('.').at(-1)?.length : undefined);
7883
+ if (scale !== undefined)
7884
+ maskOptions.scale = scale;
7885
+ this.mask = IMask(this.el, maskOptions);
7886
+ }
7887
+ }
7888
+
7889
+ // @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Color.ts
7890
+ // Built-in French translation for Pickr's UI strings, activated by
7891
+ // `data-color-locale="fr-fr"` alone - no separate options object required
7892
+ // from the consumer, unlike a fully custom `options.locale` override.
7893
+ const FR_FR = {
7894
+ 'ui:dialog': 'boîte de dialogue du sélecteur de couleur',
7895
+ 'btn:toggle': 'basculer la boîte de dialogue du sélecteur de couleur',
7896
+ 'btn:swatch': 'échantillon de couleur',
7897
+ 'btn:last-color': 'utiliser la couleur précédente',
7898
+ 'btn:save': 'Enregistrer',
7899
+ 'btn:cancel': 'Annuler',
7900
+ 'btn:clear': 'Effacer',
7901
+ 'aria:btn:save': 'enregistrer et fermer',
7902
+ 'aria:btn:cancel': 'annuler et fermer',
7903
+ 'aria:btn:clear': 'effacer et fermer',
7904
+ 'aria:input': 'champ de saisie de couleur',
7905
+ 'aria:palette': 'zone de sélection des couleurs',
7906
+ 'aria:hue': 'curseur de sélection de teinte',
7907
+ 'aria:opacity': "curseur de sélection d'opacité"
7908
+ };
7909
+ const _defaults$8 = {
7910
+ theme: 'classic',
7911
+ opacity: true,
7912
+ swatches: [
7913
+ 'rgba(244, 67, 54, 1)',
7914
+ 'rgba(233, 30, 99, 0.95)',
7915
+ 'rgba(156, 39, 176, 0.9)',
7916
+ 'rgba(103, 58, 183, 0.85)',
7917
+ 'rgba(63, 81, 181, 0.8)',
7918
+ 'rgba(33, 150, 243, 0.75)',
7919
+ 'rgba(3, 169, 244, 0.7)',
7920
+ 'rgba(0, 188, 212, 0.7)',
7921
+ 'rgba(0, 150, 136, 0.75)',
7922
+ 'rgba(76, 175, 80, 0.8)',
7923
+ 'rgba(139, 195, 74, 0.85)',
7924
+ 'rgba(205, 220, 57, 0.9)',
7925
+ 'rgba(255, 235, 59, 0.95)',
7926
+ 'rgba(255, 193, 7, 1)'
7927
+ ]
7928
+ };
7929
+ // @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Color.ts
7930
+ // Enhances a native `<input type="color" data-color-picker="pickr">` with a
7931
+ // full Pickr popup (palette, hue/opacity sliders, swatches, hex/rgba/hsla).
7932
+ // If `data-color-picker` is absent the input is left completely untouched -
7933
+ // still a plain native color swatch, matching CrazyPHP's own fallback.
7934
+ //
7935
+ // Pickr remains an optional peerDependency and is loaded on demand via peer-loader.
7936
+ class ColorInput extends Component {
7937
+ pickr;
7938
+ ready;
7939
+ _swatchEl;
7940
+ _labels;
7941
+ constructor(el, options) {
7942
+ super(el, options, ColorInput);
7943
+ this.el.M_ColorInput = this;
7944
+ this.options = {
7945
+ ...ColorInput.defaults,
7946
+ ...options
7947
+ };
7948
+ this._swatchEl = document.createElement('div');
7949
+ this.el.after(this._swatchEl);
7950
+ this.el.classList.add('hide');
7951
+ this.el.addEventListener('change', this._handleInputChange);
7952
+ this._labels = Array.from(document.querySelectorAll('label')).filter(label => label.htmlFor === this.el.id);
7953
+ this._labels.forEach(label => label.addEventListener('click', this._handleLabelClick));
7954
+ this.ready = this._setup();
7955
+ }
7956
+ static get defaults() {
7957
+ return _defaults$8;
7958
+ }
7959
+ static init(els, options = {}) {
7960
+ return super.init(els, options, ColorInput);
7961
+ }
7962
+ static getInstance(el) {
7963
+ return el.M_ColorInput;
7964
+ }
7965
+ destroy() {
7966
+ this.el.removeEventListener('change', this._handleInputChange);
7967
+ this._labels.forEach(label => label.removeEventListener('click', this._handleLabelClick));
7968
+ this.pickr?.destroyAndRemove();
7969
+ this._swatchEl.remove();
7970
+ this.el.classList.remove('hide');
7971
+ this.el.M_ColorInput = undefined;
7972
+ }
7973
+ _handleInputChange = () => {
7974
+ this.pickr?.setColor(this.el.value);
7975
+ };
7976
+ _handleLabelClick = (event) => {
7977
+ // A label associated with a hidden input[type=color] otherwise opens
7978
+ // the browser's native picker (notably in Firefox). Route the same
7979
+ // accessible label activation to Pickr instead.
7980
+ event.preventDefault();
7981
+ void this.ready.then(() => this.pickr?.show());
7982
+ };
7983
+ async _setup() {
7984
+ const Pickr = await loadPeer({
7985
+ specifier: '@simonwep/pickr',
7986
+ globalName: 'Pickr',
7987
+ feature: 'Color input (Pickr) enhancement',
7988
+ cdnHint: '<link rel="stylesheet" href="path/to/pickr-classic.min.css">\n' +
7989
+ ' <script src="path/to/pickr.min.js"></script>\n' +
7990
+ ' (self-hosted - copy from node_modules/@simonwep/pickr/dist/, or a CDN of your choice)'
7991
+ }, () => import('@simonwep/pickr'));
7992
+ const dataset = this.el.dataset;
7993
+ const theme = (['classic', 'monolith', 'nano'].includes(dataset.colorTheme ?? '')
7994
+ ? dataset.colorTheme
7995
+ : this.options.theme);
7996
+ const lockOpacity = dataset.colorOpacity !== undefined
7997
+ ? ['false', '0', '', 'null'].includes(dataset.colorOpacity)
7998
+ : !this.options.opacity;
7999
+ const pickrOptions = {
8000
+ el: this._swatchEl,
8001
+ theme,
8002
+ lockOpacity,
8003
+ swatches: this.options.swatches,
8004
+ components: {
8005
+ preview: true,
8006
+ opacity: true,
8007
+ hue: true,
8008
+ interaction: {
8009
+ hex: true,
8010
+ rgba: true,
8011
+ hsla: true,
8012
+ hsva: true,
8013
+ cmyk: true,
8014
+ input: true,
8015
+ clear: true,
8016
+ save: true
8017
+ }
8018
+ }
8019
+ };
8020
+ if (this.options.locale) {
8021
+ pickrOptions.i18n = this.options.locale;
8022
+ }
8023
+ else if (dataset.colorLocale === 'fr-fr') {
8024
+ pickrOptions.i18n = FR_FR;
8025
+ }
8026
+ const currentValue = this.el.getAttribute('value');
8027
+ if (currentValue && currentValue !== 'randomHex()') {
8028
+ pickrOptions.default = currentValue;
8029
+ }
8030
+ else if (this.el.hasAttribute('default')) {
8031
+ const currentDefault = this.el.getAttribute('default');
8032
+ if (currentDefault === 'randomHex()') {
8033
+ const randomColor = Math.floor(Math.random() * 16777216);
8034
+ pickrOptions.default = `#${randomColor.toString(16).padStart(6, '0')}`;
8035
+ }
8036
+ else if (currentDefault) {
8037
+ pickrOptions.default = currentDefault;
8038
+ }
8039
+ }
8040
+ this.pickr = Pickr.create(pickrOptions);
8041
+ if (this.el.disabled)
8042
+ this.pickr.disable();
8043
+ this.pickr.on('save', (color, instance) => {
8044
+ const hexa = color?.toHEXA().toString();
8045
+ if (hexa) {
8046
+ this.el.value = hexa;
8047
+ this.el.dispatchEvent(new Event('change', { bubbles: true }));
8048
+ }
8049
+ instance.hide();
8050
+ });
8051
+ }
8052
+ }
8053
+
8054
+ const _defaults$7 = {};
8055
+ let activeAirDatepickerField;
8056
+ // @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Date.ts
8057
+ // Enhances `<input type="text" data-type="date" data-date-picker="air-datepicker">`
8058
+ // with the air-datepicker calendar/range picker - deliberately separate from
8059
+ // kmaterialize's own native `components/datepicker/` (a different, already
8060
+ // existing class-based component); this one is opted into per-input via the
8061
+ // `data-date-picker` attribute so it never collides with `.datepicker`
8062
+ // elements. The `multiple` attribute switches the picker into range mode,
8063
+ // `data-date-format`/`data-date-lang` (only "fr-FR" ported so far) mirror
8064
+ // CrazyPHP's own dataset convention.
8065
+ //
8066
+ // air-datepicker is an optional peerDependency, loaded on demand via
8067
+ // peer-loader. Its own base stylesheet (air-datepicker/air-datepicker.css)
8068
+ // must be imported separately by the consumer - see the docs page.
8069
+ class AirDatepickerField extends Component {
8070
+ picker;
8071
+ ready;
8072
+ _triggers;
8073
+ _handleOutsideClick = (e) => {
8074
+ const calendarEl = document.querySelector('.air-datepicker');
8075
+ const clickedTrigger = this._triggers.some(trigger => trigger.contains(e.target));
8076
+ if (calendarEl instanceof HTMLElement &&
8077
+ !calendarEl.contains(e.target) &&
8078
+ !this.el.contains(e.target) &&
8079
+ !clickedTrigger) {
8080
+ this.picker?.hide();
8081
+ }
8082
+ };
8083
+ constructor(el, options) {
8084
+ super(el, options, AirDatepickerField);
8085
+ this.el.M_AirDatepickerField = this;
8086
+ this.options = {
8087
+ ...AirDatepickerField.defaults,
8088
+ ...options
8089
+ };
8090
+ const labels = Array.from(document.querySelectorAll('label')).filter(label => label.htmlFor === this.el.id);
8091
+ const icons = Array.from(this.el.parentElement?.querySelectorAll('.prefix, .suffix') ?? []);
8092
+ this._triggers = [...labels, ...icons];
8093
+ this._triggers.forEach(trigger => trigger.addEventListener('click', this._handleTriggerClick));
8094
+ this.ready = this._setup();
8095
+ }
8096
+ static get defaults() {
8097
+ return _defaults$7;
8098
+ }
8099
+ static init(els, options = {}) {
8100
+ return super.init(els, options, AirDatepickerField);
8101
+ }
8102
+ static getInstance(el) {
8103
+ return el.M_AirDatepickerField;
8104
+ }
8105
+ destroy() {
8106
+ document.removeEventListener('click', this._handleOutsideClick);
8107
+ this._triggers.forEach(trigger => trigger.removeEventListener('click', this._handleTriggerClick));
8108
+ this.picker?.destroy();
8109
+ if (activeAirDatepickerField === this)
8110
+ activeAirDatepickerField = undefined;
8111
+ this.el.M_AirDatepickerField = undefined;
8112
+ }
8113
+ _handleTriggerClick = (event) => {
8114
+ event.preventDefault();
8115
+ event.stopPropagation();
8116
+ void this.ready.then(() => this.picker?.show());
8117
+ };
8118
+ async _setup() {
8119
+ const AirDatepicker = await loadPeer({
8120
+ specifier: 'air-datepicker',
8121
+ globalName: 'AirDatepicker',
8122
+ feature: 'Date input (air-datepicker) enhancement',
8123
+ cdnHint: '<link rel="stylesheet" href="path/to/air-datepicker.css">\n' +
8124
+ ' <script src="path/to/air-datepicker.js"></script>\n' +
8125
+ ' (self-hosted - copy from node_modules/air-datepicker/, or a CDN of your choice)'
8126
+ }, () => import('air-datepicker'));
8127
+ const dataset = this.el.dataset;
8128
+ const builtInLocale = dataset.dateLang === 'fr-FR'
8129
+ ? (await import('air-datepicker/locale/fr')).default
8130
+ : (await import('air-datepicker/locale/en')).default;
8131
+ const pickerOptions = {
8132
+ dateFormat: dataset.dateFormat || 'yyyy-MM-dd',
8133
+ autoClose: dataset.dateAutoClose !== 'false',
8134
+ locale: (this.options.locale ?? builtInLocale),
8135
+ onShow: isAnimationComplete => {
8136
+ if (isAnimationComplete)
8137
+ return;
8138
+ if (activeAirDatepickerField && activeAirDatepickerField !== this) {
8139
+ activeAirDatepickerField.picker?.hide();
8140
+ }
8141
+ activeAirDatepickerField = this;
8142
+ },
8143
+ onHide: isAnimationComplete => {
8144
+ if (isAnimationComplete && activeAirDatepickerField === this) {
8145
+ activeAirDatepickerField = undefined;
8146
+ }
8147
+ }
8148
+ };
8149
+ let multiple = false;
8150
+ if (this.el.multiple || dataset.dateRange === 'true') {
8151
+ pickerOptions.range = true;
8152
+ pickerOptions.multipleDatesSeparator = ' - ';
8153
+ multiple = true;
8154
+ }
8155
+ const initialValue = this.el.getAttribute('value')?.trim();
8156
+ if (initialValue) {
8157
+ pickerOptions.selectedDates = multiple
8158
+ ? initialValue.split(' - ').map(value => value.trim())
8159
+ : [initialValue];
8160
+ }
8161
+ if (dataset.dateTimepicker === 'true') {
8162
+ pickerOptions.timepicker = true;
8163
+ if (dataset.dateTimeFormat)
8164
+ pickerOptions.timeFormat = dataset.dateTimeFormat;
8165
+ }
8166
+ const supportedViews = ['days', 'months', 'years'];
8167
+ if (supportedViews.includes(dataset.dateView ?? '')) {
8168
+ pickerOptions.view = dataset.dateView;
8169
+ }
8170
+ if (supportedViews.includes(dataset.dateMinView ?? '')) {
8171
+ pickerOptions.minView = dataset.dateMinView;
8172
+ }
8173
+ if (dataset.dateMobile === 'true')
8174
+ pickerOptions.isMobile = true;
8175
+ if (dataset.datePosition) {
8176
+ pickerOptions.position = dataset.datePosition;
8177
+ }
8178
+ if (dataset.dateButtons) {
8179
+ const supportedButtons = ['today', 'clear'];
8180
+ pickerOptions.buttons = dataset.dateButtons
8181
+ .split(',')
8182
+ .map(button => button.trim())
8183
+ .filter(button => supportedButtons.includes(button));
8184
+ }
8185
+ if (this.el.hasAttribute('min') && this.el.getAttribute('min')) {
8186
+ pickerOptions.minDate = new Date(this.el.getAttribute('min'));
8187
+ }
8188
+ if (this.el.hasAttribute('max') && this.el.getAttribute('max')) {
8189
+ pickerOptions.maxDate = new Date(this.el.getAttribute('max'));
8190
+ }
8191
+ if (!this.el.required && !pickerOptions.buttons) {
8192
+ pickerOptions.buttons = ['clear'];
8193
+ }
8194
+ pickerOptions.onSelect = ({ date }) => {
8195
+ if (multiple && Array.isArray(date) && date.length === 1)
8196
+ return;
8197
+ this.el.dispatchEvent(new Event('input', { bubbles: true }));
8198
+ this.el.dispatchEvent(new Event('change', { bubbles: true }));
8199
+ };
8200
+ this.picker = new AirDatepicker(this.el, pickerOptions);
8201
+ document.addEventListener('click', this._handleOutsideClick);
8202
+ }
8203
+ }
8204
+
8205
+ const _defaults$6 = {
8206
+ plugins: []
8207
+ };
8208
+ // Each plugin needs its own literal `import('literal-string')` call (see
8209
+ // peer-loader.ts) so Vite/webpack/Rollup can statically discover it - a
8210
+ // generic specifier-driven loop can't produce that, hence one entry per
8211
+ // plugin instead of building the import call from PLUGIN_SPECS's string.
8212
+ const PLUGIN_LOADERS = {
8213
+ 'image-preview': {
8214
+ specifier: 'filepond-plugin-image-preview',
8215
+ globalName: 'FilePondPluginImagePreview',
8216
+ importer: () => import('filepond-plugin-image-preview')
8217
+ },
8218
+ 'file-validate-type': {
8219
+ specifier: 'filepond-plugin-file-validate-type',
8220
+ globalName: 'FilePondPluginFileValidateType',
8221
+ importer: () => import('filepond-plugin-file-validate-type')
8222
+ },
8223
+ 'image-exif-orientation': {
8224
+ specifier: 'filepond-plugin-image-exif-orientation',
8225
+ globalName: 'FilePondPluginImageExifOrientation',
8226
+ importer: () => import('filepond-plugin-image-exif-orientation')
8227
+ }
8228
+ };
8229
+ // @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/File.ts
8230
+ // Enhances the EXISTING `.file-field` structure in place - add
8231
+ // `data-file-picker="filepond"` (+ optional `data-file-plugins="a,b,c"`) to
8232
+ // an otherwise completely stock `.file-field`, nothing else about the
8233
+ // markup changes. FilePond.create() hides the native `<input type=file>`
8234
+ // and injects its own root as a *sibling*, it never removes anything from
8235
+ // the DOM, so the surrounding button/label/file-path-wrapper stay intact
8236
+ // at rest - only the interactive drop area swaps in visually.
8237
+ //
8238
+ // FilePond and its plugins are optional peerDependencies, loaded on demand
8239
+ // via peer-loader.
8240
+ class FileInput extends Component {
8241
+ pond;
8242
+ ready;
8243
+ _fileEl;
8244
+ _pathEl;
8245
+ constructor(el, options) {
8246
+ super(el, options, FileInput);
8247
+ this.el.M_FileInput = this;
8248
+ const pluginsAttr = this.el.dataset.filePlugins;
8249
+ this.options = {
8250
+ ...FileInput.defaults,
8251
+ plugins: pluginsAttr
8252
+ ? pluginsAttr.split(',').map((p) => p.trim())
8253
+ : FileInput.defaults.plugins,
8254
+ ...options
8255
+ };
8256
+ this._fileEl = this.el.querySelector('input[type="file"]');
8257
+ this._pathEl = this.el.querySelector('.file-path');
8258
+ this.ready = this._setup();
8259
+ }
8260
+ static get defaults() {
8261
+ return _defaults$6;
8262
+ }
8263
+ static init(els, options = {}) {
8264
+ return super.init(els, options, FileInput);
8265
+ }
8266
+ static getInstance(el) {
8267
+ return el.M_FileInput;
8268
+ }
8269
+ destroy() {
8270
+ this.el.classList.remove('file-field-enhanced');
8271
+ this.el.querySelector('.btn')?.classList.remove('hide');
8272
+ this.el.querySelector('.file-path-wrapper')?.classList.remove('hide');
8273
+ this.pond?.destroy();
8274
+ this.el.M_FileInput = undefined;
8275
+ }
8276
+ /** Convenience wrapper over the underlying FilePond instance's own getFiles(). */
8277
+ getFiles() {
8278
+ return this.pond?.getFiles() ?? [];
8279
+ }
8280
+ async _setup() {
8281
+ if (!this._fileEl) {
8282
+ console.error(Error('.file-field enhanced with data-file-picker="filepond" needs an input[type=file]'));
8283
+ return;
8284
+ }
8285
+ const FilePond = await loadPeer({
8286
+ specifier: 'filepond',
8287
+ globalName: 'FilePond',
8288
+ feature: 'File input (FilePond) enhancement',
8289
+ cdnHint: '<link rel="stylesheet" href="path/to/filepond.min.css">\n' +
8290
+ ' <script src="path/to/filepond.min.js"></script>\n' +
8291
+ ' (self-hosted - copy from node_modules/filepond/dist/, or a CDN of your choice)'
8292
+ }, () => import('filepond'));
8293
+ const pluginNames = new Set(this.options.plugins);
8294
+ // file-validate-type is auto-registered whenever the input restricts
8295
+ // `accept`, matching CrazyPHP's own convention, even if the consumer
8296
+ // didn't list it explicitly in data-file-plugins.
8297
+ if (this._fileEl.accept)
8298
+ pluginNames.add('file-validate-type');
8299
+ for (const name of pluginNames) {
8300
+ const loader = PLUGIN_LOADERS[name];
8301
+ if (!loader)
8302
+ continue;
8303
+ const plugin = await loadPeer({
8304
+ specifier: loader.specifier,
8305
+ globalName: loader.globalName,
8306
+ feature: `File input FilePond plugin "${name}"`,
8307
+ cdnHint: `<script src="path/to/${loader.specifier}.min.js"></script> (self-hosted - copy from node_modules/${loader.specifier}/dist/, or a CDN of your choice)`
8308
+ }, loader.importer);
8309
+ FilePond.registerPlugin(plugin);
8310
+ }
8311
+ const pondOptions = {};
8312
+ const { dataset } = this._fileEl;
8313
+ const readBoolean = (value) => value !== 'false';
8314
+ pondOptions.allowMultiple = this._fileEl.multiple;
8315
+ pondOptions.disabled = this._fileEl.disabled;
8316
+ if (dataset.defaultFile)
8317
+ pondOptions.files = [dataset.defaultFile];
8318
+ if (dataset.maxFiles)
8319
+ pondOptions.maxFiles = Number(dataset.maxFiles);
8320
+ if (this._fileEl.multiple)
8321
+ pondOptions.allowReorder = readBoolean(dataset.allowReorder);
8322
+ if (dataset.labelIdle)
8323
+ pondOptions.labelIdle = dataset.labelIdle;
8324
+ if (dataset.instantUpload !== undefined)
8325
+ pondOptions.instantUpload = readBoolean(dataset.instantUpload);
8326
+ if (dataset.dropValidation !== undefined)
8327
+ pondOptions.dropValidation = readBoolean(dataset.dropValidation);
8328
+ if (dataset.imagePreviewHeight)
8329
+ pondOptions.imagePreviewHeight = Number(dataset.imagePreviewHeight);
8330
+ if (dataset.stylePanelLayout) {
8331
+ pondOptions.stylePanelLayout = dataset.stylePanelLayout;
8332
+ }
8333
+ if (dataset.stylePanelAspectRatio)
8334
+ pondOptions.stylePanelAspectRatio = dataset.stylePanelAspectRatio;
8335
+ this.pond = FilePond.create(this._fileEl, pondOptions);
8336
+ // FilePond inserts its own root as a *sibling* of the input it was
8337
+ // given - but that input lives inside .btn (stock .file-field markup
8338
+ // is `.btn > input[type=file]`), so the fresh FilePond root lands
8339
+ // inside .btn too. Move it back out to be a direct child of .file-field
8340
+ // itself before hiding .btn, or hiding .btn would take the whole
8341
+ // FilePond widget down with it.
8342
+ const pondRoot = this.pond.element;
8343
+ if (pondRoot) {
8344
+ this.el.appendChild(pondRoot);
8345
+ const fileHeight = Number(dataset.fileHeight);
8346
+ if (Number.isFinite(fileHeight) && fileHeight > 0) {
8347
+ pondRoot.style.height = `${fileHeight}px`;
8348
+ }
8349
+ // Matches CrazyPHP's own File.ts behavior - FilePond's free-tier
8350
+ // "Powered by FilePond" credit link doesn't fit a themed, branded UI.
8351
+ pondRoot.querySelectorAll('.filepond--credits').forEach((el) => el.remove());
8352
+ }
8353
+ // Everything from the stock .file-field is now redundant - FilePond's
8354
+ // own drop zone is the entire upload affordance. Hidden, not removed,
8355
+ // so the .file-field structure this component promises to preserve is
8356
+ // still there, just not shown alongside FilePond's widget.
8357
+ this.el.classList.add('file-field-enhanced');
8358
+ this.el.querySelector('.btn')?.classList.add('hide');
8359
+ this.el.querySelector('.file-path-wrapper')?.classList.add('hide');
8360
+ this._syncPathInput();
8361
+ this.pond.on('updatefiles', this._syncPathInput);
8362
+ }
8363
+ _syncPathInput = () => {
8364
+ if (!this._pathEl || !this.pond)
8365
+ return;
8366
+ this._pathEl.value = this.pond
8367
+ .getFiles()
8368
+ .map((f) => f.filename)
8369
+ .join(', ');
8370
+ };
8371
+ }
8372
+
8373
+ const _defaults$5 = {};
8374
+ function readRemoteSpec(el) {
8375
+ const raw = el.dataset.selectRemote;
8376
+ if (!raw)
8377
+ return undefined;
8378
+ try {
8379
+ return JSON.parse(raw);
8380
+ }
8381
+ catch {
8382
+ return undefined;
8383
+ }
8384
+ }
8385
+ // @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Select.ts
8386
+ // Enhances an EXISTING native `<select class="tomselected">` with Tom
8387
+ // Select - searchable, taggable, remote-loading, dependent-field wiring,
8388
+ // clear button, drag-drop reorder. Ported in a somewhat scoped form: the
8389
+ // CrazyPHP source's remote-loading path goes through its own app-specific
8390
+ // Crazyrequest/Crazyurl HTTP-client and query-param helpers, which have no
8391
+ // equivalent in a standalone UI library - this version uses a plain
8392
+ // `fetch()` instead, keeping the same `data-select-remote` JSON shape
8393
+ // (url/value/label/search/dataKey) and the same `{{field}}` render-template
8394
+ // convention.
8395
+ //
8396
+ // Coexistence with FormSelect: `.tomselected` is a recognized exclusion
8397
+ // marker in FormSelect's own AutoInit selector (see src/index.ts), so a
8398
+ // `.tomselected` select is never picked up by FormSelect regardless of
8399
+ // init ordering. This constructor still defensively tears down any
8400
+ // FormSelect instance found on the same element, belt-and-suspenders, in
8401
+ // case that selector rule is ever violated by hand.
8402
+ //
8403
+ // Tom Select is an optional peerDependency, loaded on demand via
8404
+ // peer-loader.
8405
+ class TomSelectField extends Component {
8406
+ tomSelect;
8407
+ ready;
8408
+ _dependsOnEl = null;
8409
+ constructor(el, options) {
8410
+ super(el, options, TomSelectField);
8411
+ this.el.M_TomSelectField = this;
8412
+ FormSelect.getInstance(this.el)?.destroy();
8413
+ this.options = {
8414
+ remote: readRemoteSpec(this.el),
8415
+ ...TomSelectField.defaults,
8416
+ ...options
8417
+ };
8418
+ this.ready = this._setup();
8419
+ }
8420
+ static get defaults() {
8421
+ return _defaults$5;
8422
+ }
8423
+ static init(els, options = {}) {
8424
+ return super.init(els, options, TomSelectField);
8425
+ }
8426
+ static getInstance(el) {
8427
+ return el.M_TomSelectField;
8428
+ }
8429
+ destroy() {
8430
+ if (this._dependsOnEl)
8431
+ this._dependsOnEl.removeEventListener('change', this._handleDependencyChange);
8432
+ this.tomSelect?.destroy();
8433
+ this.el.M_TomSelectField = undefined;
8434
+ }
8435
+ _handleDependencyChange = () => {
8436
+ this.tomSelect?.clear();
8437
+ this.tomSelect?.clearOptions();
8438
+ this.tomSelect?.load?.('');
8439
+ };
8440
+ async _setup() {
8441
+ const TomSelect = await loadPeer({
8442
+ specifier: 'tom-select',
8443
+ globalName: 'TomSelect',
8444
+ feature: 'Select (Tom Select) enhancement',
8445
+ cdnHint: '<script src="path/to/tom-select.complete.min.js"></script> (self-hosted - copy from node_modules/tom-select/dist/js/, or a CDN of your choice)'
8446
+ }, () => import('tom-select'));
8447
+ const dataset = this.el.dataset;
8448
+ const settings = {
8449
+ persist: false,
8450
+ createOnBlur: true,
8451
+ create: 'selectTag' in dataset,
8452
+ plugins: {},
8453
+ ...this.options.settings
8454
+ };
8455
+ if ('selectClear' in dataset) {
8456
+ settings.plugins.clear_button = { title: dataset.selectClear || 'Clear' };
8457
+ }
8458
+ if ('selectTag' in dataset || this.el.multiple) {
8459
+ settings.plugins.caret_position = {};
8460
+ settings.plugins.drag_drop = {};
8461
+ }
8462
+ if (this.el.classList.contains('icons')) {
8463
+ const renderWithIcon = (data, escape) => {
8464
+ const icon = data.icon ? `<img src="${escape(String(data.icon))}" alt="" />` : '';
8465
+ return `<div class="ts-option-content">${icon}<span>${escape(String(data.text ?? ''))}</span></div>`;
8466
+ };
8467
+ settings.render = { ...settings.render, option: renderWithIcon, item: renderWithIcon };
8468
+ }
8469
+ const remote = this.options.remote;
8470
+ if (remote) {
8471
+ settings.valueField = remote.value;
8472
+ if (remote.search)
8473
+ settings.searchField = [remote.search];
8474
+ if (remote.label && remote.label.includes('{{') && remote.label.includes('}}')) {
8475
+ const renderTemplate = (data, escape) => `<div>${remote.label.replace(/\{\{(.*?)\}\}/g, (_m, key) => escape(String(data[key] ?? '')))}</div>`;
8476
+ settings.render = { option: renderTemplate, item: renderTemplate };
8477
+ }
8478
+ else {
8479
+ settings.labelField = remote.label;
8480
+ }
8481
+ settings.load = (query, callback) => {
8482
+ fetch(remote.url)
8483
+ .then((r) => r.json())
8484
+ .then((value) => {
8485
+ const results = remote.dataKey
8486
+ ? remote.dataKey.split('.').reduce((acc, key) => acc && acc[key], value)
8487
+ : value?.results ?? value;
8488
+ callback(Array.isArray(results) ? results : []);
8489
+ })
8490
+ .catch(() => callback([]));
8491
+ };
8492
+ }
8493
+ this.tomSelect = new TomSelect(this.el, settings);
8494
+ if (dataset.depends) {
8495
+ this._dependsOnEl = document.querySelector(dataset.depends);
8496
+ this._dependsOnEl?.addEventListener('change', this._handleDependencyChange);
8497
+ }
8498
+ }
8499
+ }
8500
+
7441
8501
  const _defaults$4 = {
7442
8502
  onOpen: null,
7443
8503
  onClose: null
@@ -8318,7 +9378,7 @@ class Waves {
8318
9378
  }
8319
9379
 
8320
9380
  /* eslint-disable @typescript-eslint/no-unused-vars */
8321
- const version = '2.3.3-1.0.2';
9381
+ const version = '2.3.3-1.0.21';
8322
9382
  /**
8323
9383
  * Convenience helper matching v1's `M.toast({...})` call, since Toast is a
8324
9384
  * class in v2 with no bare functional equivalent of its own.
@@ -8333,6 +9393,8 @@ function toast(options) {
8333
9393
  */
8334
9394
  function AutoInit(context = document.body, options) {
8335
9395
  const registry = {
9396
+ Alert: context.querySelectorAll('.alert:not(.no-autoinit)'),
9397
+ Kanban: context.querySelectorAll('.kanban-board:not(.no-autoinit)'),
8336
9398
  Autocomplete: context.querySelectorAll('.autocomplete:not(.no-autoinit)'),
8337
9399
  Cards: context.querySelectorAll('.cards:not(.no-autoinit)'),
8338
9400
  Carousel: context.querySelectorAll('.carousel:not(.no-autoinit)'),
@@ -8345,15 +9407,28 @@ function AutoInit(context = document.body, options) {
8345
9407
  Parallax: context.querySelectorAll('.parallax:not(.no-autoinit)'),
8346
9408
  Pushpin: context.querySelectorAll('.pushpin:not(.no-autoinit)'),
8347
9409
  ScrollSpy: context.querySelectorAll('.scrollspy:not(.no-autoinit)'),
8348
- FormSelect: context.querySelectorAll('select:not(.no-autoinit)'),
9410
+ // Excludes .tomselected - a select opted into TomSelectField instead
9411
+ // (see below), never eligible for FormSelect regardless of ordering.
9412
+ FormSelect: context.querySelectorAll('select:not(.no-autoinit):not(.tomselected)'),
8349
9413
  Sidenav: context.querySelectorAll('.sidenav:not(.no-autoinit)'),
8350
9414
  Tabs: context.querySelectorAll('.tabs:not(.no-autoinit)'),
8351
9415
  TapTarget: context.querySelectorAll('.tap-target:not(.no-autoinit)'),
8352
9416
  Timepicker: context.querySelectorAll('.timepicker:not(.no-autoinit)'),
8353
9417
  Tooltip: context.querySelectorAll('.tooltipped:not(.no-autoinit)'),
8354
- FloatingActionButton: context.querySelectorAll('.fixed-action-btn:not(.no-autoinit)')
9418
+ FloatingActionButton: context.querySelectorAll('.fixed-action-btn:not(.no-autoinit)'),
9419
+ // Excludes .fixed-action-btn.toolbar - that's an unrelated FAB display
9420
+ // mode reusing the same class name, not this component.
9421
+ Toolbar: context.querySelectorAll('.toolbar:not(.fixed-action-btn):not(.no-autoinit)'),
9422
+ PasswordInput: context.querySelectorAll('input[data-password-toggle]:not(.no-autoinit)'),
9423
+ NumberInput: context.querySelectorAll('input[data-type="number"]:not(.no-autoinit)'),
9424
+ ColorInput: context.querySelectorAll('input[type="color"][data-color-picker="pickr"]:not(.no-autoinit)'),
9425
+ AirDatepickerField: context.querySelectorAll('input[data-date-picker="air-datepicker"]:not(.no-autoinit)'),
9426
+ FileInput: context.querySelectorAll('.file-field[data-file-picker="filepond"]:not(.no-autoinit)'),
9427
+ TomSelectField: context.querySelectorAll('select.tomselected:not(.no-autoinit)')
8355
9428
  };
8356
9429
  Autocomplete.init(registry.Autocomplete, options?.Autocomplete ?? {});
9430
+ Alert.init(registry.Alert, options?.Alert ?? {});
9431
+ Kanban.init(registry.Kanban, options?.Kanban ?? {});
8357
9432
  Cards.init(registry.Cards, options?.Cards ?? {});
8358
9433
  Carousel.init(registry.Carousel, options?.Carousel ?? {});
8359
9434
  Chips.init(registry.Chips, options?.Chips ?? {});
@@ -8372,6 +9447,13 @@ function AutoInit(context = document.body, options) {
8372
9447
  Timepicker.init(registry.Timepicker, options?.Timepicker ?? {});
8373
9448
  Tooltip.init(registry.Tooltip, options?.Tooltip ?? {});
8374
9449
  FloatingActionButton.init(registry.FloatingActionButton, options?.FloatingActionButton ?? {});
9450
+ Toolbar.init(registry.Toolbar, options?.Toolbar ?? {});
9451
+ PasswordInput.init(registry.PasswordInput, options?.PasswordInput ?? {});
9452
+ NumberInput.init(registry.NumberInput, options?.NumberInput ?? {});
9453
+ ColorInput.init(registry.ColorInput, options?.ColorInput ?? {});
9454
+ AirDatepickerField.init(registry.AirDatepickerField, options?.AirDatepickerField ?? {});
9455
+ FileInput.init(registry.FileInput, options?.FileInput ?? {});
9456
+ TomSelectField.init(registry.TomSelectField, options?.TomSelectField ?? {});
8375
9457
  }
8376
9458
  // Init
8377
9459
  if (typeof document !== 'undefined') {
@@ -8386,4 +9468,4 @@ Waves.Init();
8386
9468
  Range.Init();
8387
9469
  Cards.Init();
8388
9470
 
8389
- export { AutoInit, Autocomplete, Cards, Carousel, CharacterCounter, Chips, Collapsible, Datepicker, Dropdown, FloatingActionButton, FormSelect, Forms, Materialbox, Modal, Parallax, Pushpin, Range, ScrollSpy, Sidenav, Slider, Tabs, TapTarget, Timepicker, Toast, Tooltip, Waves, toast, version };
9471
+ export { AirDatepickerField, Alert, AutoInit, Autocomplete, Cards, Carousel, CharacterCounter, Chips, Collapsible, ColorInput, Datepicker, Dropdown, FileInput, FloatingActionButton, FormSelect, Forms, Kanban, Materialbox, Modal, NumberInput, Parallax, PasswordInput, Pushpin, Range, ScrollSpy, Sidenav, Slider, Tabs, TapTarget, Timepicker, Toast, TomSelectField, Toolbar, Tooltip, Waves, toast, version };