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.
- package/README.md +4 -4
- package/components/air-datepicker/_air-datepicker.scss +137 -0
- package/components/air-datepicker/air-datepicker.ts +192 -0
- package/components/alert/_alerts.scss +130 -0
- package/components/alert/alert.ts +73 -0
- package/components/appbar/appbar.css +301 -4
- package/components/button/_buttons.scss +39 -1
- package/components/card/_cards.scss +9 -1
- package/components/checkbox/_checkboxes.scss +120 -229
- package/components/collapsible/_collapsible.scss +50 -0
- package/components/color-input/_color-input.scss +124 -0
- package/components/color-input/color-input.ts +203 -0
- package/components/dropdown/dropdown.ts +1 -1
- package/components/file-input/_file-input-enhanced.scss +61 -0
- package/components/file-input/file-input.ts +203 -0
- package/components/kanban/_kanban.scss +148 -0
- package/components/kanban/kanban.ts +133 -0
- package/components/list/_collection.scss +10 -2
- package/components/number-input/number-input.ts +103 -0
- package/components/password-input/password-input.ts +72 -0
- package/components/slider/_range.scss +5 -1
- package/components/switch/_switches.scss +5 -0
- package/components/textfield/_input-fields.scss +4 -4
- package/components/textfield/_select.scss +54 -0
- package/components/tom-select/_tom-select-field.scss +256 -0
- package/components/tom-select/tom-select-field.ts +175 -0
- package/components/toolbar/_toolbar.scss +324 -0
- package/components/toolbar/toolbar.ts +152 -0
- package/dist/css/materialize.colors.min.css +2 -2
- package/dist/css/materialize.css +10575 -711
- package/dist/css/materialize.min.css +2 -2
- package/dist/css/materialize.min.css.map +1 -1
- package/dist/js/materialize.cjs.js +1136 -45
- package/dist/js/materialize.d.ts +227 -2
- package/dist/js/materialize.js +1136 -45
- package/dist/js/materialize.min.js +2 -2
- package/dist/js/materialize.mjs +1128 -46
- package/package.json +45 -1
- package/sass/_colors.scss +191 -5
- package/sass/_global.scss +240 -1
- package/sass/materialize.scss +14 -1
- package/sass/mixins.module.scss +6 -6
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { BaseOptions, Component, InitElements, MElement } from '../../src/component';
|
|
2
|
+
|
|
3
|
+
export interface KanbanMoveDetail {
|
|
4
|
+
card: HTMLElement;
|
|
5
|
+
from: HTMLElement;
|
|
6
|
+
to: HTMLElement;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface KanbanOptions extends BaseOptions {
|
|
10
|
+
/** Enable native drag-and-drop interactions. */
|
|
11
|
+
draggable: boolean;
|
|
12
|
+
/** Called after a card changes column or order. */
|
|
13
|
+
onMove?: (detail: KanbanMoveDetail) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const _defaults: KanbanOptions = {
|
|
17
|
+
draggable: true
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** A lightweight, dependency-free board for columns of draggable cards. */
|
|
21
|
+
export class Kanban extends Component<KanbanOptions> {
|
|
22
|
+
private _draggedCard: HTMLElement | null = null;
|
|
23
|
+
private _dragSourceColumn: HTMLElement | null = null;
|
|
24
|
+
|
|
25
|
+
private _onDragStart = (event: DragEvent) => {
|
|
26
|
+
const card = (event.target as HTMLElement | null)?.closest<HTMLElement>('.kanban-card');
|
|
27
|
+
if (!card || !this.options.draggable) return;
|
|
28
|
+
this._draggedCard = card;
|
|
29
|
+
this._dragSourceColumn = card.closest<HTMLElement>('.kanban-column');
|
|
30
|
+
card.classList.add('is-dragging');
|
|
31
|
+
event.dataTransfer?.setData('text/plain', card.dataset.kanbanCard || '');
|
|
32
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
private _onDragOver = (event: DragEvent) => {
|
|
36
|
+
if (!this._draggedCard) return;
|
|
37
|
+
const column = (event.target as HTMLElement | null)?.closest<HTMLElement>('.kanban-column');
|
|
38
|
+
if (!column || !this.el.contains(column)) return;
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
|
|
41
|
+
this.el.querySelectorAll('.kanban-column.is-drag-over').forEach((item) => item.classList.remove('is-drag-over'));
|
|
42
|
+
column.classList.add('is-drag-over');
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
private _onDrop = (event: DragEvent) => {
|
|
46
|
+
if (!this._draggedCard) return;
|
|
47
|
+
const column = (event.target as HTMLElement | null)?.closest<HTMLElement>('.kanban-column');
|
|
48
|
+
if (!column || !this.el.contains(column)) return;
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
const body = column.querySelector<HTMLElement>('.kanban-column-body') || column;
|
|
51
|
+
const targetCard = (event.target as HTMLElement | null)?.closest<HTMLElement>('.kanban-card');
|
|
52
|
+
const source = this._dragSourceColumn;
|
|
53
|
+
if (targetCard && targetCard !== this._draggedCard && targetCard.parentElement === body) {
|
|
54
|
+
const box = targetCard.getBoundingClientRect();
|
|
55
|
+
body.insertBefore(this._draggedCard, event.clientY < box.top + box.height / 2 ? targetCard : targetCard.nextSibling);
|
|
56
|
+
} else {
|
|
57
|
+
body.appendChild(this._draggedCard);
|
|
58
|
+
}
|
|
59
|
+
this._clearDragState();
|
|
60
|
+
this._updateCounts();
|
|
61
|
+
if (source && source !== column) this.options.onMove?.({ card: this._draggedCard, from: source, to: column });
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
private _onDragEnd = () => this._clearDragState();
|
|
65
|
+
|
|
66
|
+
constructor(el: HTMLElement, options: Partial<KanbanOptions>) {
|
|
67
|
+
super(el, options, Kanban);
|
|
68
|
+
this.options = { ...Kanban.defaults, ...options };
|
|
69
|
+
this.el['M_Kanban'] = this;
|
|
70
|
+
this._prepareMarkup();
|
|
71
|
+
if (this.options.draggable) this._bindEvents();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static get defaults(): KanbanOptions {
|
|
75
|
+
return _defaults;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static init(el: HTMLElement, options?: Partial<KanbanOptions>): Kanban;
|
|
79
|
+
static init(els: InitElements<MElement>, options?: Partial<KanbanOptions>): Kanban[];
|
|
80
|
+
static init(els: HTMLElement | InitElements<MElement>, options: Partial<KanbanOptions> = {}): Kanban | Kanban[] {
|
|
81
|
+
return super.init(els, options, Kanban);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static getInstance(el: HTMLElement): Kanban {
|
|
85
|
+
return el['M_Kanban'];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private _prepareMarkup() {
|
|
89
|
+
this.el.setAttribute('role', 'region');
|
|
90
|
+
this.el.querySelectorAll<HTMLElement>('.kanban-column').forEach((column) => {
|
|
91
|
+
column.setAttribute('role', 'group');
|
|
92
|
+
const body = column.querySelector<HTMLElement>('.kanban-column-body');
|
|
93
|
+
if (body) body.setAttribute('role', 'list');
|
|
94
|
+
column.querySelectorAll<HTMLElement>('.kanban-card').forEach((card) => {
|
|
95
|
+
card.setAttribute('role', 'listitem');
|
|
96
|
+
card.setAttribute('tabindex', '0');
|
|
97
|
+
if (this.options.draggable) card.draggable = true;
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
this._updateCounts();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private _bindEvents() {
|
|
104
|
+
this.el.addEventListener('dragstart', this._onDragStart);
|
|
105
|
+
this.el.addEventListener('dragover', this._onDragOver);
|
|
106
|
+
this.el.addEventListener('drop', this._onDrop);
|
|
107
|
+
this.el.addEventListener('dragend', this._onDragEnd);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private _clearDragState() {
|
|
111
|
+
this._draggedCard?.classList.remove('is-dragging');
|
|
112
|
+
this.el.querySelectorAll('.kanban-column.is-drag-over').forEach((item) => item.classList.remove('is-drag-over'));
|
|
113
|
+
this._draggedCard = null;
|
|
114
|
+
this._dragSourceColumn = null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private _updateCounts() {
|
|
118
|
+
this.el.querySelectorAll<HTMLElement>('.kanban-column').forEach((column) => {
|
|
119
|
+
const count = column.querySelector<HTMLElement>('.kanban-column-count');
|
|
120
|
+
const body = column.querySelector<HTMLElement>('.kanban-column-body');
|
|
121
|
+
if (count && body) count.textContent = String(body.querySelectorAll(':scope > .kanban-card').length);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
destroy() {
|
|
126
|
+
this._clearDragState();
|
|
127
|
+
this.el.removeEventListener('dragstart', this._onDragStart);
|
|
128
|
+
this.el.removeEventListener('dragover', this._onDragOver);
|
|
129
|
+
this.el.removeEventListener('drop', this._onDrop);
|
|
130
|
+
this.el.removeEventListener('dragend', this._onDragEnd);
|
|
131
|
+
delete this.el['M_Kanban'];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
.collection {
|
|
4
4
|
padding-left: 0;
|
|
5
5
|
list-style-type: none;
|
|
6
|
+
background-color: var(--md-sys-color-surface);
|
|
6
7
|
margin: $element-top-margin 0 $element-bottom-margin 0;
|
|
7
8
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
8
|
-
|
|
9
|
+
// @implement /Users/kzarshenas/Sites/RodeoFx/rodeo_toolkit_2/app/Front/style/scss/component_enhance.scss
|
|
10
|
+
// Matches the 12px corner radius used across cards, dropdowns and
|
|
11
|
+
// collapsibles instead of the near-square 2px default.
|
|
12
|
+
border-radius: 12px;
|
|
9
13
|
overflow: hidden;
|
|
10
14
|
position: relative;
|
|
11
15
|
|
|
@@ -89,7 +93,11 @@
|
|
|
89
93
|
|
|
90
94
|
&.with-header {
|
|
91
95
|
.collection-header {
|
|
92
|
-
|
|
96
|
+
// @implement /Users/kzarshenas/Sites/RodeoFx/rodeo_toolkit_2/app/Front/style/scss/component_enhance.scss
|
|
97
|
+
// A tinted header reads as its own row instead of blending into the
|
|
98
|
+
// list; it also isn't text, so it shouldn't be selectable.
|
|
99
|
+
background-color: var(--md-sys-color-surface-variant);
|
|
100
|
+
user-select: none;
|
|
93
101
|
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
94
102
|
padding: 10px 20px;
|
|
95
103
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type IMaskCtor from 'imask';
|
|
2
|
+
import type { MaskedNumberOptions, InputMask } from 'imask';
|
|
3
|
+
import { Component, BaseOptions, InitElements, MElement } from '../../src/component';
|
|
4
|
+
import { loadPeer } from '../../src/peer-loader';
|
|
5
|
+
|
|
6
|
+
export interface NumberInputOptions extends BaseOptions {
|
|
7
|
+
thousandsSeparator: string;
|
|
8
|
+
radix: string;
|
|
9
|
+
mapToRadix: string[];
|
|
10
|
+
min?: number;
|
|
11
|
+
max?: number;
|
|
12
|
+
scale?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const _defaults: NumberInputOptions = {
|
|
16
|
+
thousandsSeparator: ' ',
|
|
17
|
+
radix: '.',
|
|
18
|
+
mapToRadix: [',']
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Number.ts
|
|
22
|
+
// Masked/formatted numeric input via IMask (thousands separator, decimal
|
|
23
|
+
// scale, min/max) - deliberately opts in on `type="text" data-type="number"`
|
|
24
|
+
// rather than `type="number"`, so IMask can format the display value
|
|
25
|
+
// (thousands separators etc.) that a native number input wouldn't allow.
|
|
26
|
+
//
|
|
27
|
+
// IMask is an optional peerDependency, loaded on demand via peer-loader -
|
|
28
|
+
// see that file for the bundler/no-bundler resolution strategy.
|
|
29
|
+
export class NumberInput extends Component<NumberInputOptions> {
|
|
30
|
+
declare el: HTMLInputElement;
|
|
31
|
+
mask: InputMask<MaskedNumberOptions> | undefined;
|
|
32
|
+
ready: Promise<void>;
|
|
33
|
+
|
|
34
|
+
constructor(el: HTMLInputElement, options: Partial<NumberInputOptions>) {
|
|
35
|
+
super(el, options, NumberInput);
|
|
36
|
+
(this.el as any).M_NumberInput = this;
|
|
37
|
+
|
|
38
|
+
this.options = {
|
|
39
|
+
...NumberInput.defaults,
|
|
40
|
+
...options
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
this.ready = this._setup();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static get defaults(): NumberInputOptions {
|
|
47
|
+
return _defaults;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static init(el: HTMLInputElement, options?: Partial<NumberInputOptions>): NumberInput;
|
|
51
|
+
static init(
|
|
52
|
+
els: InitElements<HTMLInputElement | MElement>,
|
|
53
|
+
options?: Partial<NumberInputOptions>
|
|
54
|
+
): NumberInput[];
|
|
55
|
+
static init(
|
|
56
|
+
els: HTMLInputElement | InitElements<HTMLInputElement | MElement>,
|
|
57
|
+
options: Partial<NumberInputOptions> = {}
|
|
58
|
+
): NumberInput | NumberInput[] {
|
|
59
|
+
return super.init(els, options, NumberInput);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static getInstance(el: HTMLInputElement): NumberInput {
|
|
63
|
+
return (el as any).M_NumberInput;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
destroy() {
|
|
67
|
+
this.mask?.destroy();
|
|
68
|
+
(this.el as any).M_NumberInput = undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async _setup(): Promise<void> {
|
|
72
|
+
const IMask = await loadPeer<typeof IMaskCtor>(
|
|
73
|
+
{
|
|
74
|
+
specifier: 'imask',
|
|
75
|
+
globalName: 'IMask',
|
|
76
|
+
feature: 'Number input (IMask) enhancement',
|
|
77
|
+
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)'
|
|
78
|
+
},
|
|
79
|
+
() => import('imask')
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const maskOptions: MaskedNumberOptions = {
|
|
83
|
+
mask: Number,
|
|
84
|
+
skipInvalid: true,
|
|
85
|
+
thousandsSeparator: this.options.thousandsSeparator,
|
|
86
|
+
radix: this.options.radix,
|
|
87
|
+
mapToRadix: this.options.mapToRadix,
|
|
88
|
+
autofix: true
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const max = this.options.max ?? (this.el.hasAttribute('max') ? Number(this.el.getAttribute('max')) : undefined);
|
|
92
|
+
if (max !== undefined && !Number.isNaN(max)) maskOptions.max = max;
|
|
93
|
+
|
|
94
|
+
const min = this.options.min ?? (this.el.hasAttribute('min') ? Number(this.el.getAttribute('min')) : undefined);
|
|
95
|
+
if (min !== undefined && !Number.isNaN(min)) maskOptions.min = min;
|
|
96
|
+
|
|
97
|
+
const step = this.el.getAttribute('step');
|
|
98
|
+
const scale = this.options.scale ?? (step?.includes('.') ? step.split('.').at(-1)?.length : undefined);
|
|
99
|
+
if (scale !== undefined) maskOptions.scale = scale;
|
|
100
|
+
|
|
101
|
+
this.mask = IMask(this.el, maskOptions) as InputMask<MaskedNumberOptions>;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Component, BaseOptions, InitElements, MElement } from '../../src/component';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
4
|
+
export interface PasswordInputOptions extends BaseOptions {}
|
|
5
|
+
|
|
6
|
+
const _defaults: PasswordInputOptions = {};
|
|
7
|
+
|
|
8
|
+
// @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Password.ts
|
|
9
|
+
// Show/hide toggle for a password input - no third-party dependency, just
|
|
10
|
+
// Materialize's own .prefix/.suffix icon-slot convention (see
|
|
11
|
+
// components/textfield/_input-fields.scss), so unlike the other new form
|
|
12
|
+
// enhancements in this batch this one needs no peer-loader/dynamic import.
|
|
13
|
+
export class PasswordInput extends Component<PasswordInputOptions> {
|
|
14
|
+
declare el: HTMLInputElement;
|
|
15
|
+
private _suffixEl: HTMLElement | null;
|
|
16
|
+
|
|
17
|
+
constructor(el: HTMLInputElement, options: Partial<PasswordInputOptions>) {
|
|
18
|
+
super(el, options, PasswordInput);
|
|
19
|
+
(this.el as any).M_PasswordInput = this;
|
|
20
|
+
|
|
21
|
+
this.options = {
|
|
22
|
+
...PasswordInput.defaults,
|
|
23
|
+
...options
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
this._suffixEl = this.el.parentElement?.querySelector<HTMLElement>('[data-password-toggle-icon]') ?? null;
|
|
27
|
+
if (!this.el.dataset.passwordVisible) this.el.dataset.passwordVisible = '0';
|
|
28
|
+
this._setupEventHandlers();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static get defaults(): PasswordInputOptions {
|
|
32
|
+
return _defaults;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static init(el: HTMLInputElement, options?: Partial<PasswordInputOptions>): PasswordInput;
|
|
36
|
+
static init(
|
|
37
|
+
els: InitElements<HTMLInputElement | MElement>,
|
|
38
|
+
options?: Partial<PasswordInputOptions>
|
|
39
|
+
): PasswordInput[];
|
|
40
|
+
static init(
|
|
41
|
+
els: HTMLInputElement | InitElements<HTMLInputElement | MElement>,
|
|
42
|
+
options: Partial<PasswordInputOptions> = {}
|
|
43
|
+
): PasswordInput | PasswordInput[] {
|
|
44
|
+
return super.init(els, options, PasswordInput);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static getInstance(el: HTMLInputElement): PasswordInput {
|
|
48
|
+
return (el as any).M_PasswordInput;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
destroy() {
|
|
52
|
+
this._removeEventHandlers();
|
|
53
|
+
(this.el as any).M_PasswordInput = undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_setupEventHandlers() {
|
|
57
|
+
this._suffixEl?.addEventListener('click', this._handleToggleClick);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_removeEventHandlers() {
|
|
61
|
+
this._suffixEl?.removeEventListener('click', this._handleToggleClick);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_handleToggleClick = () => {
|
|
65
|
+
const visible = this.el.dataset.passwordVisible === '1';
|
|
66
|
+
this.el.type = visible ? 'password' : 'text';
|
|
67
|
+
this.el.dataset.passwordVisible = visible ? '0' : '1';
|
|
68
|
+
|
|
69
|
+
const iconEl = this._suffixEl?.querySelector('i');
|
|
70
|
+
if (iconEl) iconEl.textContent = visible ? 'visibility' : 'visibility_off';
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -114,7 +114,11 @@ input[type=range]::-moz-focus-inner {
|
|
|
114
114
|
|
|
115
115
|
input[type=range]::-moz-range-thumb {
|
|
116
116
|
@include range-thumb;
|
|
117
|
-
margin-top
|
|
117
|
+
// No margin-top here, unlike the -webkit-slider-thumb rule above:
|
|
118
|
+
// Firefox already vertically centers ::-moz-range-thumb on the track
|
|
119
|
+
// by default. Copying webkit's -5px correction (needed there because
|
|
120
|
+
// Chrome/Safari top-align the thumb by default) over-corrects in
|
|
121
|
+
// Firefox, pushing an already-centered thumb noticeably too high.
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
// hide the outline behind the border
|
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
opacity: 0;
|
|
24
24
|
width: 0;
|
|
25
25
|
height: 0;
|
|
26
|
+
// @implement /Users/kzarshenas/Sites/RodeoFx/rodeo_toolkit_2/app/Front/style/scss/component_enhance.scss
|
|
27
|
+
// Without this, an inherited padding can push the (invisible, 0x0)
|
|
28
|
+
// checkbox out of alignment with the lever it's meant to sit under.
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
padding: 0 !important;
|
|
26
31
|
|
|
27
32
|
// CHECKED
|
|
28
33
|
// Track
|
|
@@ -50,10 +50,10 @@ textarea.materialize-textarea {
|
|
|
50
50
|
|
|
51
51
|
// Default
|
|
52
52
|
|
|
53
|
-
input, textarea {
|
|
53
|
+
input:not([type=checkbox]):not([type=radio]), textarea {
|
|
54
54
|
box-sizing: border-box; /* https://stackoverflow.com/questions/1377719/padding-within-inputs-breaks-width-100*/
|
|
55
55
|
padding: 0 16px;
|
|
56
|
-
padding-top: 20px;
|
|
56
|
+
// padding-top: 20px;
|
|
57
57
|
//background-color: transparent; //background-color: var(--md-sys-color-surface);
|
|
58
58
|
border: none; // reset
|
|
59
59
|
border-radius: 4px; // md.sys.shape.corner.extra-small.top
|
|
@@ -180,7 +180,7 @@ textarea.materialize-textarea {
|
|
|
180
180
|
|
|
181
181
|
&.outlined {
|
|
182
182
|
|
|
183
|
-
input, textarea {
|
|
183
|
+
input:not([type=checkbox]):not([type=radio]), textarea {
|
|
184
184
|
padding-top: 0;
|
|
185
185
|
background-color: transparent; //(--md-sys-color-background);
|
|
186
186
|
border: 1px solid var(--md-sys-color-on-surface-variant);
|
|
@@ -225,7 +225,7 @@ textarea.materialize-textarea {
|
|
|
225
225
|
// border/label/text below should pick up the error color.
|
|
226
226
|
background-color: transparent;
|
|
227
227
|
|
|
228
|
-
input, textarea {
|
|
228
|
+
input:not([type=checkbox]):not([type=radio]), textarea {
|
|
229
229
|
border-color: var(--md-sys-color-error);
|
|
230
230
|
}
|
|
231
231
|
input:focus:not([readonly]), textarea:focus:not([readonly]) {
|
|
@@ -89,6 +89,60 @@ select:disabled {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// Keep enhanced FormSelect labels in sync with TomSelectField.
|
|
93
|
+
// FormSelect moves the original label inside its generated .select-wrapper.
|
|
94
|
+
.input-field:has(> .select-wrapper) {
|
|
95
|
+
margin-top: 16px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// State 1: a genuinely empty select with no visible placeholder.
|
|
99
|
+
.input-field > .select-wrapper > label {
|
|
100
|
+
position: absolute !important;
|
|
101
|
+
top: 16px !important;
|
|
102
|
+
left: 16px !important;
|
|
103
|
+
z-index: 2;
|
|
104
|
+
margin: 0 !important;
|
|
105
|
+
padding: 0 !important;
|
|
106
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
107
|
+
font-size: 16px !important;
|
|
108
|
+
line-height: 24px !important;
|
|
109
|
+
text-align: left !important;
|
|
110
|
+
background: transparent;
|
|
111
|
+
transform: none !important;
|
|
112
|
+
transform-origin: top left !important;
|
|
113
|
+
transition: top 0.2s ease-out, transform 0.2s ease-out, color 0.2s ease-out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// State 2: the generated input displays a placeholder/selection, the native
|
|
117
|
+
// select declares a placeholder, or the component currently has focus.
|
|
118
|
+
.input-field > .select-wrapper:has(> input.select-dropdown:not(:placeholder-shown)) > label,
|
|
119
|
+
.input-field > .select-wrapper:has(.hide-select > select[placeholder]:not([placeholder=""])) > label,
|
|
120
|
+
.input-field > .select-wrapper:focus-within > label {
|
|
121
|
+
position: absolute !important;
|
|
122
|
+
top: -12px !important;
|
|
123
|
+
left: 16px !important;
|
|
124
|
+
padding: 0 4px !important;
|
|
125
|
+
font-size: 16px !important;
|
|
126
|
+
background: var(--md-sys-color-surface);
|
|
127
|
+
transform: scale(0.75) !important;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.input-field > .select-wrapper:focus-within > label {
|
|
131
|
+
color: var(--input-color);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Outlined FormSelect uses the same state logic with the label in its notch.
|
|
135
|
+
.input-field.outlined > .select-wrapper:has(> input.select-dropdown:not(:placeholder-shown)) > label,
|
|
136
|
+
.input-field.outlined > .select-wrapper:has(.hide-select > select[placeholder]:not([placeholder=""])) > label,
|
|
137
|
+
.input-field.outlined > .select-wrapper:focus-within > label {
|
|
138
|
+
top: -8px !important;
|
|
139
|
+
left: 12px !important;
|
|
140
|
+
padding: 0 4px !important;
|
|
141
|
+
line-height: 16px !important;
|
|
142
|
+
background: var(--md-sys-color-surface);
|
|
143
|
+
transform: scale(0.75) !important;
|
|
144
|
+
}
|
|
145
|
+
|
|
92
146
|
// Optgroup styles
|
|
93
147
|
.select-dropdown li.optgroup {
|
|
94
148
|
border-top: 1px solid rgba(0, 0, 0, 0.04);
|