kmaterialize 2.3.3-1.0.21 → 2.3.3-1.0.22

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.
@@ -71,6 +71,7 @@
71
71
  }
72
72
 
73
73
  .kanban-card {
74
+ --kanban-card-accent: var(--md-sys-color-primary);
74
75
  position: relative;
75
76
  display: flex;
76
77
  flex-direction: column;
@@ -83,6 +84,8 @@
83
84
  box-shadow: var(--md-sys-elevation-level1, 0 1px 2px rgba(0, 0, 0, 0.14));
84
85
  cursor: grab;
85
86
  user-select: none;
87
+ overflow: hidden;
88
+ transition: transform 160ms ease, opacity 160ms ease, box-shadow 160ms ease;
86
89
 
87
90
  &:active,
88
91
  &.is-dragging {
@@ -91,12 +94,78 @@
91
94
 
92
95
  &.is-dragging {
93
96
  opacity: 0.45;
97
+ box-shadow: var(--md-sys-elevation-level3, 0 8px 18px rgba(0, 0, 0, 0.2));
94
98
  }
95
99
 
96
100
  &:focus-visible {
97
101
  outline: 2px solid var(--md-sys-color-primary);
98
102
  outline-offset: 2px;
99
103
  }
104
+
105
+ &.kanban-card-accent {
106
+ &::after {
107
+ position: absolute;
108
+ right: 0;
109
+ bottom: 0;
110
+ left: 0;
111
+ height: 0.25rem;
112
+ background: var(--kanban-card-accent);
113
+ content: '';
114
+ pointer-events: none;
115
+ }
116
+ }
117
+
118
+ &.kanban-card-accent-top::after {
119
+ top: 0;
120
+ bottom: auto;
121
+ left: 0;
122
+ width: 100%;
123
+ height: 0.25rem;
124
+ }
125
+
126
+ &.kanban-card-accent-right::after {
127
+ top: 0;
128
+ right: 0;
129
+ bottom: 0;
130
+ left: auto;
131
+ width: 0.25rem;
132
+ height: 100%;
133
+ }
134
+
135
+ &.kanban-card-accent-left::after {
136
+ top: 0;
137
+ right: auto;
138
+ bottom: 0;
139
+ left: 0;
140
+ width: 0.25rem;
141
+ height: 100%;
142
+ }
143
+
144
+ &.is-disabled,
145
+ &[aria-disabled='true'] {
146
+ cursor: not-allowed;
147
+ opacity: 0.55;
148
+ filter: saturate(0.55);
149
+ }
150
+ }
151
+
152
+ .kanban-drag-image {
153
+ position: fixed !important;
154
+ top: -10000px !important;
155
+ left: -10000px !important;
156
+ z-index: -1;
157
+ pointer-events: none;
158
+ transform: rotate(2deg) scale(1.015);
159
+ transform-origin: center;
160
+ }
161
+
162
+ .kanban-drop-preview {
163
+ min-height: 4rem;
164
+ box-sizing: border-box;
165
+ border: 2px dashed var(--md-sys-color-primary);
166
+ border-radius: 0.75rem;
167
+ background: color-mix(in srgb, var(--md-sys-color-primary-container) 45%, transparent);
168
+ pointer-events: none;
100
169
  }
101
170
 
102
171
  .kanban-card-title {
@@ -21,13 +21,24 @@ const _defaults: KanbanOptions = {
21
21
  export class Kanban extends Component<KanbanOptions> {
22
22
  private _draggedCard: HTMLElement | null = null;
23
23
  private _dragSourceColumn: HTMLElement | null = null;
24
+ private _dropPreview: HTMLElement | null = null;
25
+ private _dragImage: HTMLElement | null = null;
26
+ private _previewHeight = 64;
24
27
 
25
28
  private _onDragStart = (event: DragEvent) => {
26
29
  const card = (event.target as HTMLElement | null)?.closest<HTMLElement>('.kanban-card');
27
- if (!card || !this.options.draggable) return;
30
+ if (!card || !this.options.draggable || card.classList.contains('is-disabled') || card.getAttribute('aria-disabled') === 'true') return;
28
31
  this._draggedCard = card;
29
32
  this._dragSourceColumn = card.closest<HTMLElement>('.kanban-column');
33
+ this._previewHeight = Math.max(64, Math.round(card.getBoundingClientRect().height));
30
34
  card.classList.add('is-dragging');
35
+ this._dragImage = card.cloneNode(true) as HTMLElement;
36
+ this._dragImage.classList.remove('is-dragging');
37
+ this._dragImage.classList.add('kanban-drag-image');
38
+ this._dragImage.setAttribute('aria-hidden', 'true');
39
+ this._dragImage.style.width = `${card.getBoundingClientRect().width}px`;
40
+ document.body.appendChild(this._dragImage);
41
+ event.dataTransfer?.setDragImage(this._dragImage, card.offsetWidth / 2, card.offsetHeight / 2);
31
42
  event.dataTransfer?.setData('text/plain', card.dataset.kanbanCard || '');
32
43
  if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
33
44
  };
@@ -40,6 +51,7 @@ export class Kanban extends Component<KanbanOptions> {
40
51
  if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
41
52
  this.el.querySelectorAll('.kanban-column.is-drag-over').forEach((item) => item.classList.remove('is-drag-over'));
42
53
  column.classList.add('is-drag-over');
54
+ this._updateDropPreview(column, event);
43
55
  };
44
56
 
45
57
  private _onDrop = (event: DragEvent) => {
@@ -50,15 +62,18 @@ export class Kanban extends Component<KanbanOptions> {
50
62
  const body = column.querySelector<HTMLElement>('.kanban-column-body') || column;
51
63
  const targetCard = (event.target as HTMLElement | null)?.closest<HTMLElement>('.kanban-card');
52
64
  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);
65
+ const movedCard = this._draggedCard;
66
+ if (this._dropPreview?.parentElement === body) {
67
+ body.insertBefore(movedCard, this._dropPreview);
68
+ this._dropPreview.remove();
69
+ } else if (targetCard && targetCard !== movedCard && targetCard.parentElement === body) {
70
+ body.insertBefore(movedCard, targetCard);
56
71
  } else {
57
- body.appendChild(this._draggedCard);
72
+ body.appendChild(movedCard);
58
73
  }
59
74
  this._clearDragState();
60
75
  this._updateCounts();
61
- if (source && source !== column) this.options.onMove?.({ card: this._draggedCard, from: source, to: column });
76
+ if (source && source !== column) this.options.onMove?.({ card: movedCard, from: source, to: column });
62
77
  };
63
78
 
64
79
  private _onDragEnd = () => this._clearDragState();
@@ -93,8 +108,14 @@ export class Kanban extends Component<KanbanOptions> {
93
108
  if (body) body.setAttribute('role', 'list');
94
109
  column.querySelectorAll<HTMLElement>('.kanban-card').forEach((card) => {
95
110
  card.setAttribute('role', 'listitem');
96
- card.setAttribute('tabindex', '0');
97
- if (this.options.draggable) card.draggable = true;
111
+ const disabled = card.classList.contains('is-disabled') || card.getAttribute('aria-disabled') === 'true';
112
+ card.setAttribute('tabindex', disabled ? '-1' : '0');
113
+ if (disabled) {
114
+ card.setAttribute('aria-disabled', 'true');
115
+ card.draggable = false;
116
+ } else if (this.options.draggable) {
117
+ card.draggable = true;
118
+ }
98
119
  });
99
120
  });
100
121
  this._updateCounts();
@@ -108,10 +129,15 @@ export class Kanban extends Component<KanbanOptions> {
108
129
  }
109
130
 
110
131
  private _clearDragState() {
132
+ this._dragImage?.remove();
133
+ this._dragImage = null;
134
+ this._dropPreview?.remove();
135
+ this._dropPreview = null;
111
136
  this._draggedCard?.classList.remove('is-dragging');
112
137
  this.el.querySelectorAll('.kanban-column.is-drag-over').forEach((item) => item.classList.remove('is-drag-over'));
113
138
  this._draggedCard = null;
114
139
  this._dragSourceColumn = null;
140
+ this._previewHeight = 64;
115
141
  }
116
142
 
117
143
  private _updateCounts() {
@@ -119,9 +145,32 @@ export class Kanban extends Component<KanbanOptions> {
119
145
  const count = column.querySelector<HTMLElement>('.kanban-column-count');
120
146
  const body = column.querySelector<HTMLElement>('.kanban-column-body');
121
147
  if (count && body) count.textContent = String(body.querySelectorAll(':scope > .kanban-card').length);
148
+ const empty = body?.querySelector<HTMLElement>('.kanban-empty');
149
+ if (empty && body) empty.hidden = body.querySelectorAll(':scope > .kanban-card').length > 0;
122
150
  });
123
151
  }
124
152
 
153
+ private _updateDropPreview(column: HTMLElement, event: DragEvent) {
154
+ const body = column.querySelector<HTMLElement>('.kanban-column-body') || column;
155
+ const targetCard = (event.target as HTMLElement | null)?.closest<HTMLElement>('.kanban-card');
156
+ if (targetCard === this._draggedCard) return;
157
+ if (!this._dropPreview) {
158
+ this._dropPreview = document.createElement('div');
159
+ this._dropPreview.className = 'kanban-drop-preview';
160
+ this._dropPreview.setAttribute('aria-hidden', 'true');
161
+ }
162
+ // Keep the preview tied to the dragged card's original height. Using the
163
+ // hovered card's height makes the placeholder jump as the pointer crosses
164
+ // cards with different content lengths.
165
+ this._dropPreview.style.height = `${this._previewHeight}px`;
166
+ if (targetCard && targetCard.parentElement === body) {
167
+ const box = targetCard.getBoundingClientRect();
168
+ body.insertBefore(this._dropPreview, event.clientY < box.top + box.height / 2 ? targetCard : targetCard.nextSibling);
169
+ } else if (this._dropPreview.parentElement !== body) {
170
+ body.appendChild(this._dropPreview);
171
+ }
172
+ }
173
+
125
174
  destroy() {
126
175
  this._clearDragState();
127
176
  this.el.removeEventListener('dragstart', this._onDragStart);
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Materialize v2.3.3-1.0.21 (https://materializeweb.com)
2
+ * Materialize v2.3.3-1.0.22 (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.21 (https://materializeweb.com)
2
+ * Materialize v2.3.3-1.0.22 (https://materializeweb.com)
3
3
  * Copyright 2014-2026 Materialize
4
4
  * MIT License (https://raw.githubusercontent.com/materializecss/materialize/master/LICENSE)
5
5
  */
@@ -13983,6 +13983,7 @@ button.btn-floating {
13983
13983
  }
13984
13984
 
13985
13985
  .kanban-card {
13986
+ --kanban-card-accent: var(--md-sys-color-primary);
13986
13987
  position: relative;
13987
13988
  display: -webkit-box;
13988
13989
  display: -webkit-flex;
@@ -14007,6 +14008,11 @@ button.btn-floating {
14007
14008
  -moz-user-select: none;
14008
14009
  -ms-user-select: none;
14009
14010
  user-select: none;
14011
+ overflow: hidden;
14012
+ -webkit-transition: opacity 160ms ease, -webkit-transform 160ms ease, -webkit-box-shadow 160ms ease;
14013
+ transition: opacity 160ms ease, -webkit-transform 160ms ease, -webkit-box-shadow 160ms ease;
14014
+ transition: transform 160ms ease, opacity 160ms ease, box-shadow 160ms ease;
14015
+ transition: transform 160ms ease, opacity 160ms ease, box-shadow 160ms ease, -webkit-transform 160ms ease, -webkit-box-shadow 160ms ease;
14010
14016
  }
14011
14017
  .kanban-card:active, .kanban-card.is-dragging {
14012
14018
  cursor: -webkit-grabbing;
@@ -14014,11 +14020,74 @@ button.btn-floating {
14014
14020
  }
14015
14021
  .kanban-card.is-dragging {
14016
14022
  opacity: 0.45;
14023
+ -webkit-box-shadow: var(--md-sys-elevation-level3, 0 8px 18px rgba(0, 0, 0, 0.2));
14024
+ box-shadow: var(--md-sys-elevation-level3, 0 8px 18px rgba(0, 0, 0, 0.2));
14017
14025
  }
14018
14026
  .kanban-card:focus-visible {
14019
14027
  outline: 2px solid var(--md-sys-color-primary);
14020
14028
  outline-offset: 2px;
14021
14029
  }
14030
+ .kanban-card.kanban-card-accent::after {
14031
+ position: absolute;
14032
+ right: 0;
14033
+ bottom: 0;
14034
+ left: 0;
14035
+ height: 0.25rem;
14036
+ background: var(--kanban-card-accent);
14037
+ content: "";
14038
+ pointer-events: none;
14039
+ }
14040
+ .kanban-card.kanban-card-accent-top::after {
14041
+ top: 0;
14042
+ bottom: auto;
14043
+ left: 0;
14044
+ width: 100%;
14045
+ height: 0.25rem;
14046
+ }
14047
+ .kanban-card.kanban-card-accent-right::after {
14048
+ top: 0;
14049
+ right: 0;
14050
+ bottom: 0;
14051
+ left: auto;
14052
+ width: 0.25rem;
14053
+ height: 100%;
14054
+ }
14055
+ .kanban-card.kanban-card-accent-left::after {
14056
+ top: 0;
14057
+ right: auto;
14058
+ bottom: 0;
14059
+ left: 0;
14060
+ width: 0.25rem;
14061
+ height: 100%;
14062
+ }
14063
+ .kanban-card.is-disabled, .kanban-card[aria-disabled=true] {
14064
+ cursor: not-allowed;
14065
+ opacity: 0.55;
14066
+ -webkit-filter: saturate(0.55);
14067
+ filter: saturate(0.55);
14068
+ }
14069
+
14070
+ .kanban-drag-image {
14071
+ position: fixed !important;
14072
+ top: -10000px !important;
14073
+ left: -10000px !important;
14074
+ z-index: -1;
14075
+ pointer-events: none;
14076
+ -webkit-transform: rotate(2deg) scale(1.015);
14077
+ transform: rotate(2deg) scale(1.015);
14078
+ -webkit-transform-origin: center;
14079
+ transform-origin: center;
14080
+ }
14081
+
14082
+ .kanban-drop-preview {
14083
+ min-height: 4rem;
14084
+ -webkit-box-sizing: border-box;
14085
+ box-sizing: border-box;
14086
+ border: 2px dashed var(--md-sys-color-primary);
14087
+ border-radius: 0.75rem;
14088
+ background: color-mix(in srgb, var(--md-sys-color-primary-container) 45%, transparent);
14089
+ pointer-events: none;
14090
+ }
14022
14091
 
14023
14092
  .kanban-card-title {
14024
14093
  margin: 0;