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.
- package/README.md +4 -4
- package/components/air-datepicker/_air-datepicker.scss +137 -0
- package/components/air-datepicker/air-datepicker.js +152 -0
- package/components/air-datepicker/air-datepicker.ts +192 -0
- package/components/alert/_alerts.scss +130 -0
- package/components/alert/alert.js +54 -0
- package/components/alert/alert.ts +73 -0
- package/components/appbar/appbar.css +301 -4
- package/components/appbar/navbar-content.scss +317 -0
- package/components/appbar/navbar-scroll.scss +101 -0
- package/components/appbar/navbar-scroll.ts +67 -0
- package/components/breadcrumb/_breadcrumb.scss +16 -2
- package/components/button/_buttons.scss +39 -1
- package/components/button/buttons.js +220 -0
- package/components/card/_cards.scss +9 -1
- package/components/card/cards.js +149 -0
- package/components/carousel/carousel.js +574 -0
- package/components/carousel/slider.js +340 -0
- package/components/checkbox/_checkboxes.scss +120 -229
- package/components/chip/chips.js +341 -0
- package/components/collapsible/_collapsible.scss +51 -1
- package/components/collapsible/collapsible.js +156 -0
- package/components/color-input/_color-input.scss +124 -0
- package/components/color-input/color-input.js +166 -0
- package/components/color-input/color-input.ts +204 -0
- package/components/datepicker/datepicker.js +1035 -0
- package/components/dialog/materialbox.js +377 -0
- package/components/dialog/modal.js +72 -0
- package/components/dropdown/_dropdown.scss +7 -1
- package/components/dropdown/dropdown.js +505 -0
- package/components/dropdown/dropdown.ts +6 -3
- package/components/extensions/card-drag-handles.scss +44 -0
- package/components/extensions/card-drag-handles.ts +113 -0
- package/components/extensions/chart-connections.ts +71 -0
- package/components/extensions/chart-gestures.ts +57 -0
- package/components/extensions/chart-print.test.mjs +31 -0
- package/components/extensions/chart-print.ts +117 -0
- package/components/extensions/divider.scss +83 -0
- package/components/extensions/list.scss +193 -0
- package/components/extensions/list.ts +79 -0
- package/components/extensions/material-buttons.scss +232 -0
- package/components/extensions/material-buttons.ts +89 -0
- package/components/extensions/org-chart.scss +247 -0
- package/components/extensions/org-chart.ts +575 -0
- package/components/extensions/orgChartAvatarSpec.js +22 -0
- package/components/extensions/orgChartDragOptionsSpec.js +27 -0
- package/components/extensions/orgChartPlacementSpec.js +24 -0
- package/components/extensions/orgChartReadonlySpec.js +14 -0
- package/components/extensions/tippy.scss +11 -0
- package/components/extensions/tippy.ts +16 -0
- package/components/extensions/tooltip-factory.test.mjs +35 -0
- package/components/extensions/tooltip-factory.ts +54 -0
- package/components/extensions/tooltip-peer.ts +22 -0
- package/components/extensions/web/crazy-button.scss +88 -0
- package/components/extensions/web/crazy-button.ts +177 -0
- package/components/extensions/web/crazy-loading.scss +23 -0
- package/components/extensions/web/crazy-loading.ts +69 -0
- package/components/extensions/web/kmcomponent.ts +794 -0
- package/components/file-input/_file-input-enhanced.scss +61 -0
- package/components/file-input/file-input.js +169 -0
- package/components/file-input/file-input.ts +203 -0
- package/components/kanban/_kanban.scss +222 -0
- package/components/kanban/kanban.js +168 -0
- package/components/kanban/kanban.ts +242 -0
- package/components/list/_collection.scss +10 -2
- package/components/loading/_loading.scss +68 -0
- package/components/loading/loading.ts +104 -0
- package/components/loading/loadingSpec.js +102 -0
- package/components/maskito-input/maskito-input.ts +139 -0
- package/components/navigation-drawer/_sidenav.scss +25 -1
- package/components/navigation-drawer/navigation-rail.scss +30 -0
- package/components/navigation-drawer/sidenav.js +454 -0
- package/components/number-input/_number-input.scss +85 -0
- package/components/number-input/number-input.js +68 -0
- package/components/number-input/number-input.ts +244 -0
- package/components/otp-input/_otp-input.scss +94 -0
- package/components/otp-input/otp-input.ts +223 -0
- package/components/password-input/password-input.js +49 -0
- package/components/password-input/password-input.ts +72 -0
- package/components/popup/_popup.scss +106 -0
- package/components/popup/popup-stepper.scss +79 -0
- package/components/popup/popup-stepper.ts +162 -0
- package/components/popup/popup.ts +126 -0
- package/components/popup/popupSpec.js +157 -0
- package/components/radio-button/_radio-buttons.scss +18 -0
- package/components/rich-textarea/_rich-textarea.scss +104 -0
- package/components/rich-textarea/rich-textarea.ts +265 -0
- package/components/search/autocomplete.js +470 -0
- package/components/search/autocomplete.ts +31 -0
- package/components/search/autocompleteChipsSpec.js +20 -0
- package/components/slider/_range.scss +130 -135
- package/components/slider/range.js +169 -0
- package/components/slider/range.ts +162 -188
- package/components/snackbar/toasts.js +244 -0
- package/components/switch/_switches.scss +35 -0
- package/components/tabs/_tabs.scss +16 -0
- package/components/tabs/emptyTabsSpec.js +50 -0
- package/components/tabs/tabs.js +289 -0
- package/components/tabs/tabs.ts +12 -4
- package/components/textfield/_input-fields.scss +124 -3
- package/components/textfield/_select.scss +88 -0
- package/components/textfield/forms.js +172 -0
- package/components/textfield/forms.ts +4 -0
- package/components/textfield/input-copy.ts +52 -0
- package/components/textfield/inputCopySpec.js +39 -0
- package/components/textfield/outlined-notch.ts +81 -0
- package/components/textfield/outlinedNotchSpec.js +35 -0
- package/components/textfield/outlinedSelectSpec.js +25 -0
- package/components/textfield/select.js +385 -0
- package/components/textfield/select.ts +1 -1
- package/components/textfield/selectDropdownOwnershipSpec.js +26 -0
- package/components/timepicker/timepicker.js +698 -0
- package/components/tom-select/_tom-select-field.scss +256 -0
- package/components/tom-select/tom-select-field.js +130 -0
- package/components/tom-select/tom-select-field.ts +175 -0
- package/components/toolbar/_toolbar.scss +407 -0
- package/components/toolbar/toolbar.js +134 -0
- package/components/toolbar/toolbar.ts +152 -0
- package/components/tooltip/tooltip.js +270 -0
- package/dist/css/materialize.colors.min.css +2 -2
- package/dist/css/materialize.css +18289 -4692
- package/dist/css/materialize.min.css +2 -2
- package/dist/css/materialize.min.css.map +1 -1
- package/dist/js/materialize.cjs.js +4937 -259
- package/dist/js/materialize.d.ts +1106 -28
- package/dist/js/materialize.js +4937 -259
- package/dist/js/materialize.min.js +21 -2
- package/dist/js/materialize.mjs +4911 -260
- package/dist/js/tippy.cjs.js +53 -0
- package/dist/js/tippy.d.ts +10 -0
- package/dist/js/tippy.mjs +51 -0
- package/package.json +76 -2
- package/sass/_colors.scss +191 -5
- package/sass/_global.scss +240 -1
- package/sass/enhancement/jspreadsheet_materializecss.scss +329 -0
- package/sass/enhancement/jsuites_materializecss.scss +413 -0
- package/sass/enhancement/spreadsheet.scss +2 -0
- package/sass/enhancement/spreadsheet_editors.scss +99 -0
- package/sass/enhancement/textfield_layout.scss +17 -0
- package/sass/materialize.scss +34 -0
- 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/
|
|
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)
|
|
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/
|
|
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.
|
|
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
|
+
}
|