kmaterialize 2.3.3-1.0.23 → 2.3.3-1.0.24

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.
@@ -116,7 +116,8 @@ export class ColorInput extends Component<ColorInputOptions> {
116
116
  }
117
117
 
118
118
  _handleInputChange = () => {
119
- this.pickr?.setColor(this.el.value);
119
+ // A programmatic sync must not emit save and recursively dispatch change.
120
+ this.pickr?.setColor(this.el.value, true);
120
121
  };
121
122
 
122
123
  _handleLabelClick = (event: MouseEvent) => {
@@ -1,15 +1,20 @@
1
1
  .kanban-board {
2
+ --kanban-zoom: 1;
2
3
  --kanban-column-width: 20rem;
3
4
  --kanban-gap: 1rem;
4
5
  display: flex;
5
6
  align-items: flex-start;
6
- gap: var(--kanban-gap);
7
+ gap: calc(var(--kanban-gap) * var(--kanban-zoom));
7
8
  width: 100%;
8
9
  padding: 0.25rem;
9
10
  overflow-x: auto;
10
11
  box-sizing: border-box;
11
12
  overscroll-behavior-x: contain;
12
13
 
14
+ // Unlike transform, zoom reserves the scaled space in the flex layout.
15
+ // This also keeps wrapping and the scrollable area aligned with the cards.
16
+ & > .kanban-column { zoom: var(--kanban-zoom); }
17
+
13
18
  &.kanban-wrap {
14
19
  flex-wrap: wrap;
15
20
  overflow-x: visible;
@@ -79,8 +84,8 @@
79
84
  padding: 1rem;
80
85
  border: 1px solid var(--md-sys-color-outline-variant);
81
86
  border-radius: 0.75rem;
82
- background: var(--md-sys-color-surface);
83
- color: var(--md-sys-color-on-surface);
87
+ background: var(--kanban-card-color, var(--md-sys-color-surface));
88
+ color: var(--kanban-card-text, var(--md-sys-color-on-surface));
84
89
  box-shadow: var(--md-sys-elevation-level1, 0 1px 2px rgba(0, 0, 0, 0.14));
85
90
  cursor: grab;
86
91
  user-select: none;
@@ -11,6 +11,20 @@ export interface KanbanOptions extends BaseOptions {
11
11
  draggable: boolean;
12
12
  /** Called after a card changes column or order. */
13
13
  onMove?: (detail: KanbanMoveDetail) => void;
14
+ zoom?: number;
15
+ minZoom?: number;
16
+ maxZoom?: number;
17
+ }
18
+
19
+ /** Optional per-card appearance overrides. */
20
+ export interface KanbanCardAppearance {
21
+ /** Exact background color, overriding the Materialize surface color. */
22
+ color?: string;
23
+ /** Optional text and icon color. */
24
+ textColor?: string;
25
+ /** Optional edge accent color and position. */
26
+ accent?: string;
27
+ accentPosition?: 'top' | 'right' | 'bottom' | 'left';
14
28
  }
15
29
 
16
30
  const _defaults: KanbanOptions = {
@@ -19,6 +33,7 @@ const _defaults: KanbanOptions = {
19
33
 
20
34
  /** A lightweight, dependency-free board for columns of draggable cards. */
21
35
  export class Kanban extends Component<KanbanOptions> {
36
+ private _zoom = 1;
22
37
  private _draggedCard: HTMLElement | null = null;
23
38
  private _dragSourceColumn: HTMLElement | null = null;
24
39
  private _dropPreview: HTMLElement | null = null;
@@ -30,13 +45,15 @@ export class Kanban extends Component<KanbanOptions> {
30
45
  if (!card || !this.options.draggable || card.classList.contains('is-disabled') || card.getAttribute('aria-disabled') === 'true') return;
31
46
  this._draggedCard = card;
32
47
  this._dragSourceColumn = card.closest<HTMLElement>('.kanban-column');
33
- this._previewHeight = Math.max(64, Math.round(card.getBoundingClientRect().height));
48
+ this._previewHeight = card.offsetHeight;
34
49
  card.classList.add('is-dragging');
35
50
  this._dragImage = card.cloneNode(true) as HTMLElement;
36
51
  this._dragImage.classList.remove('is-dragging');
37
52
  this._dragImage.classList.add('kanban-drag-image');
38
53
  this._dragImage.setAttribute('aria-hidden', 'true');
39
- this._dragImage.style.width = `${card.getBoundingClientRect().width}px`;
54
+ this._dragImage.style.width = `${card.offsetWidth}px`;
55
+ this._dragImage.style.transform = `scale(${this._zoom})`;
56
+ this._dragImage.style.transformOrigin = 'top left';
40
57
  document.body.appendChild(this._dragImage);
41
58
  event.dataTransfer?.setDragImage(this._dragImage, card.offsetWidth / 2, card.offsetHeight / 2);
42
59
  event.dataTransfer?.setData('text/plain', card.dataset.kanbanCard || '');
@@ -81,13 +98,41 @@ export class Kanban extends Component<KanbanOptions> {
81
98
  constructor(el: HTMLElement, options: Partial<KanbanOptions>) {
82
99
  super(el, options, Kanban);
83
100
  this.options = { ...Kanban.defaults, ...options };
101
+ this._zoom = this._clampZoom(this.options.zoom ?? 1);
84
102
  this.el['M_Kanban'] = this;
85
103
  this._prepareMarkup();
104
+ this._applyZoom();
86
105
  if (this.options.draggable) this._bindEvents();
87
106
  }
88
107
 
89
108
  static get defaults(): KanbanOptions {
90
- return _defaults;
109
+ return { ..._defaults, zoom: 1, minZoom: 0.5, maxZoom: 2 };
110
+ }
111
+
112
+ /** Set the board scale, clamped to the configured limits. */
113
+ setZoom(value: number): void {
114
+ if (!Number.isFinite(value)) throw new TypeError('Zoom must be finite.');
115
+ this._zoom = this._clampZoom(value);
116
+ this._applyZoom();
117
+ }
118
+
119
+ /** Return the current board scale multiplier. */
120
+ getZoom(): number { return this._zoom; }
121
+
122
+ /** Restore the board to 100% scale. */
123
+ resetZoom(): void { this.setZoom(1); }
124
+
125
+ private _clampZoom(value: number): number {
126
+ if (!Number.isFinite(value)) throw new TypeError('Zoom must be finite.');
127
+ const min = this.options.minZoom ?? 0.5;
128
+ const max = this.options.maxZoom ?? 2;
129
+ if (!Number.isFinite(min) || !Number.isFinite(max) || min <= 0 || max < min) throw new TypeError('Invalid zoom limits.');
130
+ return Math.min(max, Math.max(min, value));
131
+ }
132
+
133
+ private _applyZoom(): void {
134
+ this.el.style.setProperty('--kanban-zoom', String(this._zoom));
135
+ this.el.dataset.zoom = String(this._zoom);
91
136
  }
92
137
 
93
138
  static init(el: HTMLElement, options?: Partial<KanbanOptions>): Kanban;
@@ -107,6 +152,21 @@ export class Kanban extends Component<KanbanOptions> {
107
152
  const body = column.querySelector<HTMLElement>('.kanban-column-body');
108
153
  if (body) body.setAttribute('role', 'list');
109
154
  column.querySelectorAll<HTMLElement>('.kanban-card').forEach((card) => {
155
+ // Optional inline item colors override the Materialize defaults while
156
+ // keeping the public markup independent from the Sass implementation.
157
+ const color = card.dataset.kanbanColor;
158
+ const textColor = card.dataset.kanbanTextColor;
159
+ const accent = card.dataset.kanbanAccent;
160
+ const accentPosition = card.dataset.kanbanAccentPosition;
161
+ if (color && CSS.supports('color', color)) card.style.setProperty('--kanban-card-color', color);
162
+ if (textColor && CSS.supports('color', textColor)) card.style.setProperty('--kanban-card-text', textColor);
163
+ if (accent && CSS.supports('color', accent)) {
164
+ card.style.setProperty('--kanban-card-accent', accent);
165
+ card.classList.add('kanban-card-accent');
166
+ }
167
+ if (['top', 'right', 'bottom', 'left'].includes(accentPosition || '')) {
168
+ card.classList.add(`kanban-card-accent-${accentPosition}`);
169
+ }
110
170
  card.setAttribute('role', 'listitem');
111
171
  const disabled = card.classList.contains('is-disabled') || card.getAttribute('aria-disabled') === 'true';
112
172
  card.setAttribute('tabindex', disabled ? '-1' : '0');
@@ -163,16 +163,21 @@ textarea.materialize-textarea {
163
163
  user-select: none;
164
164
  }
165
165
 
166
- .prefix ~ input, .prefix ~ textarea {
166
+ .prefix ~ input:not([type=checkbox]):not([type=radio]), .prefix ~ textarea {
167
167
  padding-left: calc(12px + 24px + 16px);
168
168
  }
169
- .suffix ~ input, .suffix ~ textarea {
169
+ .suffix ~ input:not([type=checkbox]):not([type=radio]), .suffix ~ textarea {
170
170
  padding-right: calc(12px + 24px + 16px);
171
171
  }
172
172
  .prefix ~ label {
173
173
  left: calc(12px + 24px + 16px);
174
174
  }
175
175
 
176
+ // Reserve space beneath an already-floating label even before focus.
177
+ &:not(.outlined) > input:not([type=checkbox]):not([type=radio]):not(:focus):not(:placeholder-shown) {
178
+ padding-top: 20px;
179
+ }
180
+
176
181
  // Outlined
177
182
 
178
183
  // todo: use this approach for floating labels with outline
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Materialize v2.3.3-1.0.23 (https://materializeweb.com)
2
+ * Materialize v2.3.3-1.0.24 (https://materializeweb.com)
3
3
  * Copyright 2014-2026 Materialize
4
4
  * MIT License (https://raw.githubusercontent.com/materializecss/materialize/master/LICENSE)
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Materialize v2.3.3-1.0.23 (https://materializeweb.com)
2
+ * Materialize v2.3.3-1.0.24 (https://materializeweb.com)
3
3
  * Copyright 2014-2026 Materialize
4
4
  * MIT License (https://raw.githubusercontent.com/materializecss/materialize/master/LICENSE)
5
5
  */
@@ -13864,6 +13864,7 @@ button.btn-floating {
13864
13864
  }
13865
13865
  }
13866
13866
  .kanban-board {
13867
+ --kanban-zoom: 1;
13867
13868
  --kanban-column-width: 20rem;
13868
13869
  --kanban-gap: 1rem;
13869
13870
  display: -webkit-box;
@@ -13874,7 +13875,7 @@ button.btn-floating {
13874
13875
  -webkit-align-items: flex-start;
13875
13876
  -ms-flex-align: start;
13876
13877
  align-items: flex-start;
13877
- gap: var(--kanban-gap);
13878
+ gap: calc(var(--kanban-gap) * var(--kanban-zoom));
13878
13879
  width: 100%;
13879
13880
  padding: 0.25rem;
13880
13881
  overflow-x: auto;
@@ -13882,6 +13883,9 @@ button.btn-floating {
13882
13883
  box-sizing: border-box;
13883
13884
  overscroll-behavior-x: contain;
13884
13885
  }
13886
+ .kanban-board > .kanban-column {
13887
+ zoom: var(--kanban-zoom);
13888
+ }
13885
13889
  .kanban-board.kanban-wrap {
13886
13890
  -webkit-flex-wrap: wrap;
13887
13891
  -ms-flex-wrap: wrap;
@@ -13998,8 +14002,8 @@ button.btn-floating {
13998
14002
  padding: 1rem;
13999
14003
  border: 1px solid var(--md-sys-color-outline-variant);
14000
14004
  border-radius: 0.75rem;
14001
- background: var(--md-sys-color-surface);
14002
- color: var(--md-sys-color-on-surface);
14005
+ background: var(--kanban-card-color, var(--md-sys-color-surface));
14006
+ color: var(--kanban-card-text, var(--md-sys-color-on-surface));
14003
14007
  -webkit-box-shadow: var(--md-sys-elevation-level1, 0 1px 2px rgba(0, 0, 0, 0.14));
14004
14008
  box-shadow: var(--md-sys-elevation-level1, 0 1px 2px rgba(0, 0, 0, 0.14));
14005
14009
  cursor: -webkit-grab;
@@ -19009,15 +19013,24 @@ textarea.materialize-textarea {
19009
19013
  -ms-user-select: none;
19010
19014
  user-select: none;
19011
19015
  }
19012
- .input-field .prefix ~ input, .input-field .prefix ~ textarea {
19016
+ .input-field .prefix ~ input:not([type=checkbox]):not([type=radio]), .input-field .prefix ~ textarea {
19013
19017
  padding-left: 52px;
19014
19018
  }
19015
- .input-field .suffix ~ input, .input-field .suffix ~ textarea {
19019
+ .input-field .suffix ~ input:not([type=checkbox]):not([type=radio]), .input-field .suffix ~ textarea {
19016
19020
  padding-right: 52px;
19017
19021
  }
19018
19022
  .input-field .prefix ~ label {
19019
19023
  left: 52px;
19020
19024
  }
19025
+ .input-field:not(.outlined) > input:not([type=checkbox]):not([type=radio]):not(:focus):not(:-moz-placeholder) {
19026
+ padding-top: 20px;
19027
+ }
19028
+ .input-field:not(.outlined) > input:not([type=checkbox]):not([type=radio]):not(:focus):not(:-ms-input-placeholder) {
19029
+ padding-top: 20px;
19030
+ }
19031
+ .input-field:not(.outlined) > input:not([type=checkbox]):not([type=radio]):not(:focus):not(:placeholder-shown) {
19032
+ padding-top: 20px;
19033
+ }
19021
19034
  .input-field.outlined input:not([type=checkbox]):not([type=radio]), .input-field.outlined textarea {
19022
19035
  padding-top: 0;
19023
19036
  background-color: transparent;