kmaterialize 2.3.3-1.0.3 → 2.3.3-1.0.30

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 (141) 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.js +152 -0
  4. package/components/air-datepicker/air-datepicker.ts +192 -0
  5. package/components/alert/_alerts.scss +130 -0
  6. package/components/alert/alert.js +54 -0
  7. package/components/alert/alert.ts +73 -0
  8. package/components/appbar/appbar.css +301 -4
  9. package/components/appbar/navbar-content.scss +317 -0
  10. package/components/appbar/navbar-scroll.scss +101 -0
  11. package/components/appbar/navbar-scroll.ts +67 -0
  12. package/components/breadcrumb/_breadcrumb.scss +16 -2
  13. package/components/button/_buttons.scss +39 -1
  14. package/components/button/buttons.js +220 -0
  15. package/components/card/_cards.scss +9 -1
  16. package/components/card/cards.js +149 -0
  17. package/components/carousel/carousel.js +574 -0
  18. package/components/carousel/slider.js +340 -0
  19. package/components/checkbox/_checkboxes.scss +120 -229
  20. package/components/chip/chips.js +341 -0
  21. package/components/collapsible/_collapsible.scss +51 -1
  22. package/components/collapsible/collapsible.js +156 -0
  23. package/components/color-input/_color-input.scss +124 -0
  24. package/components/color-input/color-input.js +166 -0
  25. package/components/color-input/color-input.ts +204 -0
  26. package/components/datepicker/datepicker.js +1035 -0
  27. package/components/dialog/materialbox.js +377 -0
  28. package/components/dialog/modal.js +72 -0
  29. package/components/dropdown/_dropdown.scss +7 -1
  30. package/components/dropdown/dropdown.js +505 -0
  31. package/components/dropdown/dropdown.ts +6 -3
  32. package/components/extensions/card-drag-handles.scss +44 -0
  33. package/components/extensions/card-drag-handles.ts +113 -0
  34. package/components/extensions/chart-connections.ts +71 -0
  35. package/components/extensions/chart-gestures.ts +57 -0
  36. package/components/extensions/chart-print.test.mjs +31 -0
  37. package/components/extensions/chart-print.ts +117 -0
  38. package/components/extensions/divider.scss +83 -0
  39. package/components/extensions/list.scss +193 -0
  40. package/components/extensions/list.ts +79 -0
  41. package/components/extensions/material-buttons.scss +232 -0
  42. package/components/extensions/material-buttons.ts +89 -0
  43. package/components/extensions/org-chart.scss +247 -0
  44. package/components/extensions/org-chart.ts +575 -0
  45. package/components/extensions/orgChartAvatarSpec.js +22 -0
  46. package/components/extensions/orgChartDragOptionsSpec.js +27 -0
  47. package/components/extensions/orgChartPlacementSpec.js +24 -0
  48. package/components/extensions/orgChartReadonlySpec.js +14 -0
  49. package/components/extensions/tippy.scss +11 -0
  50. package/components/extensions/tippy.ts +16 -0
  51. package/components/extensions/tooltip-factory.test.mjs +35 -0
  52. package/components/extensions/tooltip-factory.ts +54 -0
  53. package/components/extensions/tooltip-peer.ts +22 -0
  54. package/components/extensions/web/crazy-button.scss +88 -0
  55. package/components/extensions/web/crazy-button.ts +177 -0
  56. package/components/extensions/web/crazy-loading.scss +23 -0
  57. package/components/extensions/web/crazy-loading.ts +69 -0
  58. package/components/extensions/web/kmcomponent.ts +794 -0
  59. package/components/file-input/_file-input-enhanced.scss +61 -0
  60. package/components/file-input/file-input.js +169 -0
  61. package/components/file-input/file-input.ts +203 -0
  62. package/components/kanban/_kanban.scss +222 -0
  63. package/components/kanban/kanban.js +168 -0
  64. package/components/kanban/kanban.ts +242 -0
  65. package/components/list/_collection.scss +10 -2
  66. package/components/loading/_loading.scss +68 -0
  67. package/components/loading/loading.ts +104 -0
  68. package/components/loading/loadingSpec.js +102 -0
  69. package/components/maskito-input/maskito-input.ts +139 -0
  70. package/components/navigation-drawer/_sidenav.scss +25 -1
  71. package/components/navigation-drawer/navigation-rail.scss +30 -0
  72. package/components/navigation-drawer/sidenav.js +454 -0
  73. package/components/number-input/_number-input.scss +85 -0
  74. package/components/number-input/number-input.js +68 -0
  75. package/components/number-input/number-input.ts +244 -0
  76. package/components/otp-input/_otp-input.scss +94 -0
  77. package/components/otp-input/otp-input.ts +223 -0
  78. package/components/password-input/password-input.js +49 -0
  79. package/components/password-input/password-input.ts +72 -0
  80. package/components/popup/_popup.scss +106 -0
  81. package/components/popup/popup-stepper.scss +79 -0
  82. package/components/popup/popup-stepper.ts +162 -0
  83. package/components/popup/popup.ts +126 -0
  84. package/components/popup/popupSpec.js +157 -0
  85. package/components/radio-button/_radio-buttons.scss +18 -0
  86. package/components/rich-textarea/_rich-textarea.scss +104 -0
  87. package/components/rich-textarea/rich-textarea.ts +265 -0
  88. package/components/search/autocomplete.js +470 -0
  89. package/components/search/autocomplete.ts +31 -0
  90. package/components/search/autocompleteChipsSpec.js +20 -0
  91. package/components/slider/_range.scss +130 -135
  92. package/components/slider/range.js +169 -0
  93. package/components/slider/range.ts +162 -188
  94. package/components/snackbar/toasts.js +244 -0
  95. package/components/switch/_switches.scss +35 -0
  96. package/components/tabs/_tabs.scss +16 -0
  97. package/components/tabs/emptyTabsSpec.js +50 -0
  98. package/components/tabs/tabs.js +289 -0
  99. package/components/tabs/tabs.ts +12 -4
  100. package/components/textfield/_input-fields.scss +124 -3
  101. package/components/textfield/_select.scss +88 -0
  102. package/components/textfield/forms.js +172 -0
  103. package/components/textfield/forms.ts +4 -0
  104. package/components/textfield/input-copy.ts +52 -0
  105. package/components/textfield/inputCopySpec.js +39 -0
  106. package/components/textfield/outlined-notch.ts +81 -0
  107. package/components/textfield/outlinedNotchSpec.js +35 -0
  108. package/components/textfield/outlinedSelectSpec.js +25 -0
  109. package/components/textfield/select.js +385 -0
  110. package/components/textfield/select.ts +1 -1
  111. package/components/textfield/selectDropdownOwnershipSpec.js +26 -0
  112. package/components/timepicker/timepicker.js +698 -0
  113. package/components/tom-select/_tom-select-field.scss +256 -0
  114. package/components/tom-select/tom-select-field.js +130 -0
  115. package/components/tom-select/tom-select-field.ts +175 -0
  116. package/components/toolbar/_toolbar.scss +407 -0
  117. package/components/toolbar/toolbar.js +134 -0
  118. package/components/toolbar/toolbar.ts +152 -0
  119. package/components/tooltip/tooltip.js +270 -0
  120. package/dist/css/materialize.colors.min.css +2 -2
  121. package/dist/css/materialize.css +18289 -4692
  122. package/dist/css/materialize.min.css +2 -2
  123. package/dist/css/materialize.min.css.map +1 -1
  124. package/dist/js/materialize.cjs.js +4937 -259
  125. package/dist/js/materialize.d.ts +1106 -28
  126. package/dist/js/materialize.js +4937 -259
  127. package/dist/js/materialize.min.js +21 -2
  128. package/dist/js/materialize.mjs +4911 -260
  129. package/dist/js/tippy.cjs.js +53 -0
  130. package/dist/js/tippy.d.ts +10 -0
  131. package/dist/js/tippy.mjs +51 -0
  132. package/package.json +76 -2
  133. package/sass/_colors.scss +191 -5
  134. package/sass/_global.scss +240 -1
  135. package/sass/enhancement/jspreadsheet_materializecss.scss +329 -0
  136. package/sass/enhancement/jsuites_materializecss.scss +413 -0
  137. package/sass/enhancement/spreadsheet.scss +2 -0
  138. package/sass/enhancement/spreadsheet_editors.scss +99 -0
  139. package/sass/enhancement/textfield_layout.scss +17 -0
  140. package/sass/materialize.scss +34 -0
  141. package/sass/mixins.module.scss +6 -6
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  ## Getting started
16
16
  Read the [getting started guide](https://materialize.kevinzarshenas.com/getting-started.html) for more information on how to use kmaterialize.
17
17
 
18
- - Clone the repo: `git clone https://github.com/materializecss/materialize.git`
18
+ - Clone the repo: `git clone https://github.com/kekefreedog/kmaterialize.git`
19
19
  - Install with [npm](https://www.npmjs.com): `npm install kmaterialize`
20
20
 
21
21
  ## Testing
@@ -28,10 +28,10 @@ The documentation can be found at <https://materialize.kevinzarshenas.com>. To r
28
28
  This is the core project with all the components. To see how they are used we recommend using an example project or go to the documentation.
29
29
 
30
30
  ### Running documentation locally
31
- See [Materialize Docs Repo](https://github.com/materializecss/materialize-docs) to see Materialize in Action.
31
+ kmaterialize doesn't have its own docs site yet. See the upstream [Materialize Docs Repo](https://github.com/materializecss/materialize-docs) for an example of Materialize's components in action.
32
32
 
33
33
  ### Releases
34
- Previous releases are available [here](https://github.com/materializecss/materialize/releases). You can also find the changelog under each release.
34
+ Previous releases are available [here](https://github.com/kekefreedog/kmaterialize/releases). You can also find the changelog under each release.
35
35
 
36
36
  ## Supported Browsers:
37
37
  Materialize is compatible with:
@@ -49,7 +49,7 @@ We use Jasmine as our testing framework and we're trying to write a robust test
49
49
  For quick testing install live-server node package globally and run `npm run demo`
50
50
 
51
51
  ## Contributing
52
- Check out the [CONTRIBUTING document](docs/CONTRIBUTING.md) in the root of the repository to learn how you can contribute. You can also browse the [help-wanted](https://github.com/materializecss/materialize/labels/help-wanted) tag in our issue tracker to find things to do.
52
+ Check out the [CONTRIBUTING document](docs/CONTRIBUTING.md) in the root of the repository to learn how you can contribute.
53
53
 
54
54
  ## Copyright and license
55
55
  Code Copyright 2026 kmaterialize. Code released under the MIT license.
@@ -0,0 +1,137 @@
1
+ // @implement /Users/kzarshenas/Sites/RodeoFx/rodeo_toolkit_2/app/Front/style/scss/enhancement/airdatepicker_materializecss.scss
2
+ // Remaps air-datepicker's own --adp-* custom properties onto MD3 tokens so
3
+ // its calendar popup auto-themes with the rest of kmaterialize, instead of
4
+ // theming the popup's own markup directly. Requires the consumer to
5
+ // separately import air-datepicker's own base CSS (`air-datepicker/air-datepicker.css`) -
6
+ // kmaterialize can't reach into an optional peer's package at build time
7
+ // without turning it into a hard dependency, so this partial only overrides
8
+ // the custom properties that stylesheet defines.
9
+ //
10
+ // This is the richer Rodeo toolkit variant of the port (rounded corners,
11
+ // circular day cells, weekend/other-month readability fixes, a two-line
12
+ // nav title) rather than CrazyPHP's plainer original.
13
+ input[data-date-picker='air-datepicker'][readonly] {
14
+ caret-color: transparent;
15
+ cursor: pointer;
16
+ }
17
+
18
+ .input-field:has(input[data-date-picker='air-datepicker']) {
19
+ > label,
20
+ .prefix,
21
+ .suffix {
22
+ z-index: 1;
23
+ cursor: pointer;
24
+ }
25
+ }
26
+
27
+ .air-datepicker {
28
+ --adp-z-index: 100001; // above modals/SweetAlert-style overlays
29
+ --adp-transition-duration: 0.11s;
30
+
31
+ --adp-font-family: 'Roboto', sans-serif;
32
+ --adp-background-color: var(--md-sys-color-surface);
33
+ --adp-color: var(--md-sys-color-on-surface);
34
+ --adp-color-secondary: var(--md-sys-color-on-surface-variant);
35
+ --adp-border-color: var(--md-sys-color-outline-variant);
36
+ --adp-border-color-inner: var(--md-sys-color-outline-variant);
37
+
38
+ --adp-color-current-date: var(--md-sys-color-primary);
39
+
40
+ --adp-background-color-hover: var(--md-sys-color-surface-variant);
41
+ --adp-background-color-active: var(--md-sys-color-surface-variant);
42
+
43
+ --adp-cell-background-color-selected: var(--md-sys-color-primary);
44
+ --adp-cell-background-color-selected-hover: var(--md-sys-color-primary-container);
45
+ --adp-cell-border-color-in-range: var(--md-sys-color-primary);
46
+ --adp-cell-background-color-in-range: var(--md-sys-color-primary-container);
47
+ --adp-cell-background-color-in-range-hover: var(--md-sys-color-primary-container);
48
+ --adp-color-other-month: var(--md-sys-color-outline);
49
+ --adp-color-other-month-hover: var(--md-sys-color-on-surface-variant);
50
+
51
+ --adp-btn-color: var(--md-sys-color-primary);
52
+ --adp-btn-color-hover: var(--md-sys-color-on-primary);
53
+ --adp-btn-background-color-hover: var(--md-sys-color-primary-container);
54
+ --adp-btn-background-color-active: var(--md-sys-color-primary-container);
55
+
56
+ --adp-time-track-color: var(--md-sys-color-outline-variant);
57
+ --adp-time-track-color-hover: var(--md-sys-color-primary);
58
+ --adp-time-day-period-color: var(--md-sys-color-on-surface-variant);
59
+
60
+ // Shape - rounded popup, circular day cells, generous padding, matching
61
+ // the rest of kmaterialize's MD3 shape language (pill toolbars, rounded
62
+ // cards) rather than air-datepicker's own square default.
63
+ --adp-border-radius: 24px;
64
+ --adp-cell-border-radius: 50%;
65
+ --adp-padding: 16px;
66
+
67
+ --adp-color-disabled: var(--md-sys-color-outline);
68
+ --adp-color-disabled-in-range: var(--md-sys-color-on-surface-variant);
69
+
70
+ .air-datepicker-nav {
71
+ height: auto;
72
+ min-height: 32px;
73
+ padding: 0px;
74
+ box-shadow: initial;
75
+ background-color: inherit;
76
+ color: var(--adp-nav-color-secondary) !important;
77
+
78
+ .air-datepicker-nav--title {
79
+ display: flex;
80
+ flex-direction: column;
81
+ align-items: center;
82
+ font-size: 14px;
83
+ line-height: 1.25;
84
+
85
+ i {
86
+ font-size: 14px;
87
+ }
88
+
89
+ // A second line under the month/year, for context (e.g. what range
90
+ // is about to apply) - opt-in, air-datepicker doesn't emit this
91
+ // itself, a consumer's own `dateFormatter` can inject it.
92
+ small {
93
+ font-size: 10.5px;
94
+ font-weight: 500;
95
+ letter-spacing: 0.03em;
96
+ font-variant-numeric: tabular-nums;
97
+ color: var(--adp-color-secondary);
98
+ }
99
+ }
100
+
101
+ // Material icons sit larger than the arrows they replace.
102
+ .air-datepicker-nav--action i {
103
+ font-size: 18px;
104
+ }
105
+ }
106
+
107
+ .air-datepicker-cell {
108
+ font-variant-numeric: tabular-nums;
109
+ }
110
+
111
+ // air-datepicker tags weekends and ships no style for them.
112
+ .air-datepicker-cell.-day-.-weekend- {
113
+ color: var(--md-sys-color-outline);
114
+ }
115
+
116
+ .air-datepicker-body--day-name.-weekend- {
117
+ opacity: 0.7;
118
+ }
119
+
120
+ // Inside a selected range a weekend has to stay readable.
121
+ .air-datepicker-cell.-day-.-weekend-.-in-range-,
122
+ .air-datepicker-cell.-day-.-weekend-.-selected- {
123
+ color: inherit;
124
+ }
125
+
126
+ // The view holds still when a greyed (other-month) day is picked, so the
127
+ // selection can land off-month - without this it reads as nothing
128
+ // selected.
129
+ .air-datepicker-cell.-day-.-other-month-.-selected- {
130
+ color: var(--md-sys-color-on-primary);
131
+ background: var(--md-sys-color-primary);
132
+ }
133
+
134
+ i.air-datepicker--pointer {
135
+ display: none;
136
+ }
137
+ }
@@ -0,0 +1,152 @@
1
+ import { Component } from '../../src/component';
2
+ import { loadPeer } from '../../src/peer-loader';
3
+ const _defaults = {};
4
+ let activeAirDatepickerField;
5
+ // @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Date.ts
6
+ // Enhances `<input type="text" data-type="date" data-date-picker="air-datepicker">`
7
+ // with the air-datepicker calendar/range picker - deliberately separate from
8
+ // kmaterialize's own native `components/datepicker/` (a different, already
9
+ // existing class-based component); this one is opted into per-input via the
10
+ // `data-date-picker` attribute so it never collides with `.datepicker`
11
+ // elements. The `multiple` attribute switches the picker into range mode,
12
+ // `data-date-format`/`data-date-lang` (only "fr-FR" ported so far) mirror
13
+ // CrazyPHP's own dataset convention.
14
+ //
15
+ // air-datepicker is an optional peerDependency, loaded on demand via
16
+ // peer-loader. Its own base stylesheet (air-datepicker/air-datepicker.css)
17
+ // must be imported separately by the consumer - see the docs page.
18
+ export class AirDatepickerField extends Component {
19
+ picker;
20
+ ready;
21
+ _triggers;
22
+ _handleOutsideClick = (e) => {
23
+ const calendarEl = document.querySelector('.air-datepicker');
24
+ const clickedTrigger = this._triggers.some(trigger => trigger.contains(e.target));
25
+ if (calendarEl instanceof HTMLElement &&
26
+ !calendarEl.contains(e.target) &&
27
+ !this.el.contains(e.target) &&
28
+ !clickedTrigger) {
29
+ this.picker?.hide();
30
+ }
31
+ };
32
+ constructor(el, options) {
33
+ super(el, options, AirDatepickerField);
34
+ this.el.M_AirDatepickerField = this;
35
+ this.options = {
36
+ ...AirDatepickerField.defaults,
37
+ ...options
38
+ };
39
+ const labels = Array.from(document.querySelectorAll('label')).filter(label => label.htmlFor === this.el.id);
40
+ const icons = Array.from(this.el.parentElement?.querySelectorAll('.prefix, .suffix') ?? []);
41
+ this._triggers = [...labels, ...icons];
42
+ this._triggers.forEach(trigger => trigger.addEventListener('click', this._handleTriggerClick));
43
+ this.ready = this._setup();
44
+ }
45
+ static get defaults() {
46
+ return _defaults;
47
+ }
48
+ static init(els, options = {}) {
49
+ return super.init(els, options, AirDatepickerField);
50
+ }
51
+ static getInstance(el) {
52
+ return el.M_AirDatepickerField;
53
+ }
54
+ destroy() {
55
+ document.removeEventListener('click', this._handleOutsideClick);
56
+ this._triggers.forEach(trigger => trigger.removeEventListener('click', this._handleTriggerClick));
57
+ this.picker?.destroy();
58
+ if (activeAirDatepickerField === this)
59
+ activeAirDatepickerField = undefined;
60
+ this.el.M_AirDatepickerField = undefined;
61
+ }
62
+ _handleTriggerClick = (event) => {
63
+ event.preventDefault();
64
+ event.stopPropagation();
65
+ void this.ready.then(() => this.picker?.show());
66
+ };
67
+ async _setup() {
68
+ const AirDatepicker = await loadPeer({
69
+ specifier: 'air-datepicker',
70
+ globalName: 'AirDatepicker',
71
+ feature: 'Date input (air-datepicker) enhancement',
72
+ cdnHint: '<link rel="stylesheet" href="path/to/air-datepicker.css">\n' +
73
+ ' <script src="path/to/air-datepicker.js"></script>\n' +
74
+ ' (self-hosted - copy from node_modules/air-datepicker/, or a CDN of your choice)'
75
+ }, () => import('air-datepicker'));
76
+ const dataset = this.el.dataset;
77
+ const builtInLocale = dataset.dateLang === 'fr-FR'
78
+ ? (await import('air-datepicker/locale/fr')).default
79
+ : (await import('air-datepicker/locale/en')).default;
80
+ const pickerOptions = {
81
+ dateFormat: dataset.dateFormat || 'yyyy-MM-dd',
82
+ autoClose: dataset.dateAutoClose !== 'false',
83
+ locale: (this.options.locale ?? builtInLocale),
84
+ onShow: isAnimationComplete => {
85
+ if (isAnimationComplete)
86
+ return;
87
+ if (activeAirDatepickerField && activeAirDatepickerField !== this) {
88
+ activeAirDatepickerField.picker?.hide();
89
+ }
90
+ activeAirDatepickerField = this;
91
+ },
92
+ onHide: isAnimationComplete => {
93
+ if (isAnimationComplete && activeAirDatepickerField === this) {
94
+ activeAirDatepickerField = undefined;
95
+ }
96
+ }
97
+ };
98
+ let multiple = false;
99
+ if (this.el.multiple || dataset.dateRange === 'true') {
100
+ pickerOptions.range = true;
101
+ pickerOptions.multipleDatesSeparator = ' - ';
102
+ multiple = true;
103
+ }
104
+ const initialValue = this.el.getAttribute('value')?.trim();
105
+ if (initialValue) {
106
+ pickerOptions.selectedDates = multiple
107
+ ? initialValue.split(' - ').map(value => value.trim())
108
+ : [initialValue];
109
+ }
110
+ if (dataset.dateTimepicker === 'true') {
111
+ pickerOptions.timepicker = true;
112
+ if (dataset.dateTimeFormat)
113
+ pickerOptions.timeFormat = dataset.dateTimeFormat;
114
+ }
115
+ const supportedViews = ['days', 'months', 'years'];
116
+ if (supportedViews.includes(dataset.dateView ?? '')) {
117
+ pickerOptions.view = dataset.dateView;
118
+ }
119
+ if (supportedViews.includes(dataset.dateMinView ?? '')) {
120
+ pickerOptions.minView = dataset.dateMinView;
121
+ }
122
+ if (dataset.dateMobile === 'true')
123
+ pickerOptions.isMobile = true;
124
+ if (dataset.datePosition) {
125
+ pickerOptions.position = dataset.datePosition;
126
+ }
127
+ if (dataset.dateButtons) {
128
+ const supportedButtons = ['today', 'clear'];
129
+ pickerOptions.buttons = dataset.dateButtons
130
+ .split(',')
131
+ .map(button => button.trim())
132
+ .filter(button => supportedButtons.includes(button));
133
+ }
134
+ if (this.el.hasAttribute('min') && this.el.getAttribute('min')) {
135
+ pickerOptions.minDate = new Date(this.el.getAttribute('min'));
136
+ }
137
+ if (this.el.hasAttribute('max') && this.el.getAttribute('max')) {
138
+ pickerOptions.maxDate = new Date(this.el.getAttribute('max'));
139
+ }
140
+ if (!this.el.required && !pickerOptions.buttons) {
141
+ pickerOptions.buttons = ['clear'];
142
+ }
143
+ pickerOptions.onSelect = ({ date }) => {
144
+ if (multiple && Array.isArray(date) && date.length === 1)
145
+ return;
146
+ this.el.dispatchEvent(new Event('input', { bubbles: true }));
147
+ this.el.dispatchEvent(new Event('change', { bubbles: true }));
148
+ };
149
+ this.picker = new AirDatepicker(this.el, pickerOptions);
150
+ document.addEventListener('click', this._handleOutsideClick);
151
+ }
152
+ }
@@ -0,0 +1,192 @@
1
+ import type AirDatepickerCtor from 'air-datepicker';
2
+ import type { AirDatepickerOptions } from 'air-datepicker';
3
+ import { Component, BaseOptions, InitElements, MElement } from '../../src/component';
4
+ import { loadPeer } from '../../src/peer-loader';
5
+
6
+ export interface AirDatepickerFieldOptions extends BaseOptions {
7
+ locale?: unknown;
8
+ }
9
+
10
+ const _defaults: AirDatepickerFieldOptions = {};
11
+ let activeAirDatepickerField: AirDatepickerField | undefined;
12
+
13
+ // @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Date.ts
14
+ // Enhances `<input type="text" data-type="date" data-date-picker="air-datepicker">`
15
+ // with the air-datepicker calendar/range picker - deliberately separate from
16
+ // kmaterialize's own native `components/datepicker/` (a different, already
17
+ // existing class-based component); this one is opted into per-input via the
18
+ // `data-date-picker` attribute so it never collides with `.datepicker`
19
+ // elements. The `multiple` attribute switches the picker into range mode,
20
+ // `data-date-format`/`data-date-lang` (only "fr-FR" ported so far) mirror
21
+ // CrazyPHP's own dataset convention.
22
+ //
23
+ // air-datepicker is an optional peerDependency, loaded on demand via
24
+ // peer-loader. Its own base stylesheet (air-datepicker/air-datepicker.css)
25
+ // must be imported separately by the consumer - see the docs page.
26
+ export class AirDatepickerField extends Component<AirDatepickerFieldOptions> {
27
+ declare el: HTMLInputElement;
28
+ picker: AirDatepickerCtor | undefined;
29
+ ready: Promise<void>;
30
+ private _triggers: HTMLElement[];
31
+ private _handleOutsideClick = (e: MouseEvent) => {
32
+ const calendarEl = document.querySelector('.air-datepicker');
33
+ const clickedTrigger = this._triggers.some(trigger => trigger.contains(e.target as Node));
34
+ if (
35
+ calendarEl instanceof HTMLElement &&
36
+ !calendarEl.contains(e.target as Node) &&
37
+ !this.el.contains(e.target as Node) &&
38
+ !clickedTrigger
39
+ ) {
40
+ this.picker?.hide();
41
+ }
42
+ };
43
+
44
+ constructor(el: HTMLInputElement, options: Partial<AirDatepickerFieldOptions>) {
45
+ super(el, options, AirDatepickerField);
46
+ (this.el as any).M_AirDatepickerField = this;
47
+
48
+ this.options = {
49
+ ...AirDatepickerField.defaults,
50
+ ...options
51
+ };
52
+
53
+ const labels = Array.from(document.querySelectorAll('label')).filter(
54
+ label => label.htmlFor === this.el.id
55
+ );
56
+ const icons = Array.from(
57
+ this.el.parentElement?.querySelectorAll<HTMLElement>('.prefix, .suffix') ?? []
58
+ );
59
+ this._triggers = [...labels, ...icons];
60
+ this._triggers.forEach(trigger => trigger.addEventListener('click', this._handleTriggerClick));
61
+ this.ready = this._setup();
62
+ }
63
+
64
+ static get defaults(): AirDatepickerFieldOptions {
65
+ return _defaults;
66
+ }
67
+
68
+ static init(el: HTMLInputElement, options?: Partial<AirDatepickerFieldOptions>): AirDatepickerField;
69
+ static init(
70
+ els: InitElements<HTMLInputElement | MElement>,
71
+ options?: Partial<AirDatepickerFieldOptions>
72
+ ): AirDatepickerField[];
73
+ static init(
74
+ els: HTMLInputElement | InitElements<HTMLInputElement | MElement>,
75
+ options: Partial<AirDatepickerFieldOptions> = {}
76
+ ): AirDatepickerField | AirDatepickerField[] {
77
+ return super.init(els, options, AirDatepickerField);
78
+ }
79
+
80
+ static getInstance(el: HTMLInputElement): AirDatepickerField {
81
+ return (el as any).M_AirDatepickerField;
82
+ }
83
+
84
+ destroy() {
85
+ document.removeEventListener('click', this._handleOutsideClick);
86
+ this._triggers.forEach(trigger => trigger.removeEventListener('click', this._handleTriggerClick));
87
+ this.picker?.destroy();
88
+ if (activeAirDatepickerField === this) activeAirDatepickerField = undefined;
89
+ (this.el as any).M_AirDatepickerField = undefined;
90
+ }
91
+
92
+ private _handleTriggerClick = (event: MouseEvent) => {
93
+ event.preventDefault();
94
+ event.stopPropagation();
95
+ void this.ready.then(() => this.picker?.show());
96
+ };
97
+
98
+ async _setup(): Promise<void> {
99
+ const AirDatepicker = await loadPeer<typeof AirDatepickerCtor>(
100
+ {
101
+ specifier: 'air-datepicker',
102
+ globalName: 'AirDatepicker',
103
+ feature: 'Date input (air-datepicker) enhancement',
104
+ cdnHint:
105
+ '<link rel="stylesheet" href="path/to/air-datepicker.css">\n' +
106
+ ' <script src="path/to/air-datepicker.js"></script>\n' +
107
+ ' (self-hosted - copy from node_modules/air-datepicker/, or a CDN of your choice)'
108
+ },
109
+ () => import('air-datepicker')
110
+ );
111
+
112
+ const dataset = this.el.dataset;
113
+ const builtInLocale = dataset.dateLang === 'fr-FR'
114
+ ? (await import('air-datepicker/locale/fr.js')).default
115
+ : (await import('air-datepicker/locale/en.js')).default;
116
+ const pickerOptions: AirDatepickerOptions = {
117
+ dateFormat: dataset.dateFormat || 'yyyy-MM-dd',
118
+ autoClose: dataset.dateAutoClose !== 'false',
119
+ locale: (this.options.locale ?? builtInLocale) as AirDatepickerOptions['locale'],
120
+ onShow: isAnimationComplete => {
121
+ if (isAnimationComplete) return;
122
+ if (activeAirDatepickerField && activeAirDatepickerField !== this) {
123
+ activeAirDatepickerField.picker?.hide();
124
+ }
125
+ activeAirDatepickerField = this;
126
+ },
127
+ onHide: isAnimationComplete => {
128
+ if (isAnimationComplete && activeAirDatepickerField === this) {
129
+ activeAirDatepickerField = undefined;
130
+ }
131
+ }
132
+ };
133
+
134
+ let multiple = false;
135
+ if (this.el.multiple || dataset.dateRange === 'true') {
136
+ pickerOptions.range = true;
137
+ pickerOptions.multipleDatesSeparator = ' - ';
138
+ multiple = true;
139
+ }
140
+
141
+ const initialValue = this.el.getAttribute('value')?.trim();
142
+ if (initialValue) {
143
+ pickerOptions.selectedDates = multiple
144
+ ? initialValue.split(' - ').map(value => value.trim())
145
+ : [initialValue];
146
+ }
147
+
148
+ if (dataset.dateTimepicker === 'true') {
149
+ pickerOptions.timepicker = true;
150
+ if (dataset.dateTimeFormat) pickerOptions.timeFormat = dataset.dateTimeFormat;
151
+ }
152
+
153
+ const supportedViews = ['days', 'months', 'years'];
154
+ if (supportedViews.includes(dataset.dateView ?? '')) {
155
+ pickerOptions.view = dataset.dateView as AirDatepickerOptions['view'];
156
+ }
157
+ if (supportedViews.includes(dataset.dateMinView ?? '')) {
158
+ pickerOptions.minView = dataset.dateMinView as AirDatepickerOptions['minView'];
159
+ }
160
+
161
+ if (dataset.dateMobile === 'true') pickerOptions.isMobile = true;
162
+ if (dataset.datePosition) {
163
+ pickerOptions.position = dataset.datePosition as AirDatepickerOptions['position'];
164
+ }
165
+ if (dataset.dateButtons) {
166
+ const supportedButtons = ['today', 'clear'];
167
+ pickerOptions.buttons = dataset.dateButtons
168
+ .split(',')
169
+ .map(button => button.trim())
170
+ .filter(button => supportedButtons.includes(button)) as AirDatepickerOptions['buttons'];
171
+ }
172
+
173
+ if (this.el.hasAttribute('min') && this.el.getAttribute('min')) {
174
+ pickerOptions.minDate = new Date(this.el.getAttribute('min') as string);
175
+ }
176
+ if (this.el.hasAttribute('max') && this.el.getAttribute('max')) {
177
+ pickerOptions.maxDate = new Date(this.el.getAttribute('max') as string);
178
+ }
179
+ if (!this.el.required && !pickerOptions.buttons) {
180
+ pickerOptions.buttons = ['clear'];
181
+ }
182
+
183
+ pickerOptions.onSelect = ({ date }: any) => {
184
+ if (multiple && Array.isArray(date) && date.length === 1) return;
185
+ this.el.dispatchEvent(new Event('input', { bubbles: true }));
186
+ this.el.dispatchEvent(new Event('change', { bubbles: true }));
187
+ };
188
+
189
+ this.picker = new AirDatepicker(this.el, pickerOptions);
190
+ document.addEventListener('click', this._handleOutsideClick);
191
+ }
192
+ }
@@ -0,0 +1,130 @@
1
+ // Alert banners provide persistent, contextual feedback in a page.
2
+ .alert {
3
+ --alert-container: var(--md-sys-color-surface-variant);
4
+ --alert-on-container: var(--md-sys-color-on-surface-variant);
5
+ --alert-border: var(--md-sys-color-outline-variant);
6
+
7
+ position: relative;
8
+ display: flex;
9
+ align-items: flex-start;
10
+ gap: 0.875rem;
11
+ width: 100%;
12
+ box-sizing: border-box;
13
+ padding: 1rem 1.125rem;
14
+ margin: 0 0 1rem;
15
+ border: 1px solid transparent;
16
+ border-radius: 0.75rem;
17
+ background: var(--alert-container);
18
+ color: var(--alert-on-container);
19
+ line-height: 1.5;
20
+
21
+ .alert-icon {
22
+ flex: 0 0 auto;
23
+ align-self: center;
24
+ margin-top: 0;
25
+ font-size: 1.375rem;
26
+ line-height: 1;
27
+ }
28
+
29
+ .alert-image {
30
+ width: 2.25rem;
31
+ height: 2.25rem;
32
+ border-radius: 50%;
33
+ object-fit: cover;
34
+ }
35
+
36
+ .alert-content {
37
+ min-width: 0;
38
+ flex: 1 1 auto;
39
+ }
40
+
41
+ .alert-title {
42
+ display: block;
43
+ margin: 0 0 0.25rem;
44
+ font-weight: 600;
45
+ }
46
+
47
+ .alert-message {
48
+ margin: 0;
49
+ }
50
+
51
+ .alert-close {
52
+ display: inline-flex;
53
+ flex: 0 0 auto;
54
+ align-items: center;
55
+ justify-content: center;
56
+ width: 2rem;
57
+ height: 2rem;
58
+ margin: -0.25rem -0.375rem -0.25rem 0;
59
+ padding: 0;
60
+ border: 0;
61
+ border-radius: 50%;
62
+ background: transparent;
63
+ color: inherit;
64
+ cursor: pointer;
65
+
66
+ &:hover,
67
+ &:focus-visible {
68
+ background: color-mix(in srgb, currentColor 12%, transparent);
69
+ }
70
+
71
+ .material-icons {
72
+ font-size: 1.25rem;
73
+ }
74
+ }
75
+
76
+ &.alert-info {
77
+ --alert-container: var(--md-sys-color-secondary-container);
78
+ --alert-on-container: var(--md-sys-color-on-secondary-container);
79
+ --alert-border: var(--md-sys-color-secondary);
80
+ }
81
+
82
+ &.alert-success {
83
+ --alert-container: var(--md-sys-color-tertiary-container);
84
+ --alert-on-container: var(--md-sys-color-on-tertiary-container);
85
+ --alert-border: var(--md-sys-color-tertiary);
86
+ }
87
+
88
+ &.alert-warning {
89
+ --alert-container: var(--md-sys-color-primary-container);
90
+ --alert-on-container: var(--md-sys-color-on-primary-container);
91
+ --alert-border: var(--md-sys-color-primary);
92
+ }
93
+
94
+ &.alert-error {
95
+ --alert-container: var(--md-sys-color-error-container);
96
+ --alert-on-container: var(--md-sys-color-on-error-container);
97
+ --alert-border: var(--md-sys-color-error);
98
+ }
99
+
100
+ &.alert-outlined {
101
+ border-color: var(--alert-border);
102
+ background: transparent;
103
+ }
104
+
105
+ &.alert-filled {
106
+ border-color: var(--alert-border);
107
+ background: var(--alert-border);
108
+ color: var(--md-sys-color-on-primary);
109
+ }
110
+
111
+ &.alert-compact {
112
+ align-items: center;
113
+ padding-block: 0.625rem;
114
+ }
115
+
116
+ &[hidden] {
117
+ display: none;
118
+ }
119
+ }
120
+
121
+ @media (prefers-reduced-motion: no-preference) {
122
+ .alert {
123
+ transition: opacity 180ms ease, transform 180ms ease;
124
+
125
+ &.alert-dismissing {
126
+ opacity: 0;
127
+ transform: translateY(-0.25rem);
128
+ }
129
+ }
130
+ }