kmaterialize 2.3.3-1.0.20 → 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.
@@ -0,0 +1,148 @@
1
+ .kanban-board {
2
+ --kanban-column-width: 20rem;
3
+ --kanban-gap: 1rem;
4
+ display: flex;
5
+ align-items: flex-start;
6
+ gap: var(--kanban-gap);
7
+ width: 100%;
8
+ padding: 0.25rem;
9
+ overflow-x: auto;
10
+ box-sizing: border-box;
11
+ overscroll-behavior-x: contain;
12
+
13
+ &.kanban-wrap {
14
+ flex-wrap: wrap;
15
+ overflow-x: visible;
16
+ }
17
+ }
18
+
19
+ .kanban-column {
20
+ display: flex;
21
+ flex: 0 0 var(--kanban-column-width);
22
+ flex-direction: column;
23
+ min-height: 15rem;
24
+ padding: 0.75rem;
25
+ border: 1px solid var(--md-sys-color-outline-variant);
26
+ border-radius: 0.875rem;
27
+ background: var(--md-sys-color-surface-variant);
28
+ box-sizing: border-box;
29
+ transition: border-color 160ms ease, background-color 160ms ease;
30
+
31
+ &.is-drag-over {
32
+ border-color: var(--md-sys-color-primary);
33
+ background: color-mix(in srgb, var(--md-sys-color-primary-container) 55%, var(--md-sys-color-surface-variant));
34
+ }
35
+ }
36
+
37
+ .kanban-column-header {
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: space-between;
41
+ gap: 0.5rem;
42
+ padding: 0.25rem 0.25rem 0.75rem;
43
+ color: var(--md-sys-color-on-surface-variant);
44
+ }
45
+
46
+ .kanban-column-title {
47
+ margin: 0;
48
+ font-size: 1rem;
49
+ font-weight: 600;
50
+ }
51
+
52
+ .kanban-column-count {
53
+ display: inline-flex;
54
+ min-width: 1.5rem;
55
+ min-height: 1.5rem;
56
+ align-items: center;
57
+ justify-content: center;
58
+ padding: 0 0.375rem;
59
+ border-radius: 999px;
60
+ background: var(--md-sys-color-surface);
61
+ color: var(--md-sys-color-on-surface);
62
+ font-size: 0.75rem;
63
+ }
64
+
65
+ .kanban-column-body {
66
+ display: flex;
67
+ flex: 1 1 auto;
68
+ flex-direction: column;
69
+ gap: 0.75rem;
70
+ min-height: 8rem;
71
+ }
72
+
73
+ .kanban-card {
74
+ position: relative;
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 0.625rem;
78
+ padding: 1rem;
79
+ border: 1px solid var(--md-sys-color-outline-variant);
80
+ border-radius: 0.75rem;
81
+ background: var(--md-sys-color-surface);
82
+ color: var(--md-sys-color-on-surface);
83
+ box-shadow: var(--md-sys-elevation-level1, 0 1px 2px rgba(0, 0, 0, 0.14));
84
+ cursor: grab;
85
+ user-select: none;
86
+
87
+ &:active,
88
+ &.is-dragging {
89
+ cursor: grabbing;
90
+ }
91
+
92
+ &.is-dragging {
93
+ opacity: 0.45;
94
+ }
95
+
96
+ &:focus-visible {
97
+ outline: 2px solid var(--md-sys-color-primary);
98
+ outline-offset: 2px;
99
+ }
100
+ }
101
+
102
+ .kanban-card-title {
103
+ margin: 0;
104
+ font-size: 1rem;
105
+ font-weight: 600;
106
+ }
107
+
108
+ .kanban-card-description {
109
+ margin: 0;
110
+ color: var(--md-sys-color-on-surface-variant);
111
+ font-size: 0.875rem;
112
+ }
113
+
114
+ .kanban-card-meta {
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: space-between;
118
+ gap: 0.5rem;
119
+ color: var(--md-sys-color-on-surface-variant);
120
+ font-size: 0.75rem;
121
+ }
122
+
123
+ .kanban-card-label {
124
+ display: inline-flex;
125
+ align-items: center;
126
+ align-self: flex-start;
127
+ padding: 0.1875rem 0.5rem;
128
+ border-radius: 999px;
129
+ background: var(--md-sys-color-secondary-container);
130
+ color: var(--md-sys-color-on-secondary-container);
131
+ font-size: 0.75rem;
132
+ }
133
+
134
+ .kanban-empty {
135
+ display: grid;
136
+ min-height: 5rem;
137
+ place-items: center;
138
+ border: 1px dashed var(--md-sys-color-outline);
139
+ border-radius: 0.625rem;
140
+ color: var(--md-sys-color-on-surface-variant);
141
+ font-size: 0.875rem;
142
+ }
143
+
144
+ @media (max-width: 600px) {
145
+ .kanban-board {
146
+ --kanban-column-width: min(20rem, calc(100vw - 3rem));
147
+ }
148
+ }
@@ -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
+ }
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Materialize v2.3.3-1.0.20 (https://materializeweb.com)
2
+ * Materialize v2.3.3-1.0.21 (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.20 (https://materializeweb.com)
2
+ * Materialize v2.3.3-1.0.21 (https://materializeweb.com)
3
3
  * Copyright 2014-2026 Materialize
4
4
  * MIT License (https://raw.githubusercontent.com/materializecss/materialize/master/LICENSE)
5
5
  */
@@ -13863,6 +13863,227 @@ button.btn-floating {
13863
13863
  transform: translateY(-0.25rem);
13864
13864
  }
13865
13865
  }
13866
+ .kanban-board {
13867
+ --kanban-column-width: 20rem;
13868
+ --kanban-gap: 1rem;
13869
+ display: -webkit-box;
13870
+ display: -webkit-flex;
13871
+ display: -ms-flexbox;
13872
+ display: flex;
13873
+ -webkit-box-align: start;
13874
+ -webkit-align-items: flex-start;
13875
+ -ms-flex-align: start;
13876
+ align-items: flex-start;
13877
+ gap: var(--kanban-gap);
13878
+ width: 100%;
13879
+ padding: 0.25rem;
13880
+ overflow-x: auto;
13881
+ -webkit-box-sizing: border-box;
13882
+ box-sizing: border-box;
13883
+ overscroll-behavior-x: contain;
13884
+ }
13885
+ .kanban-board.kanban-wrap {
13886
+ -webkit-flex-wrap: wrap;
13887
+ -ms-flex-wrap: wrap;
13888
+ flex-wrap: wrap;
13889
+ overflow-x: visible;
13890
+ }
13891
+
13892
+ .kanban-column {
13893
+ display: -webkit-box;
13894
+ display: -webkit-flex;
13895
+ display: -ms-flexbox;
13896
+ display: flex;
13897
+ -webkit-box-flex: 0;
13898
+ -webkit-flex: 0 0 var(--kanban-column-width);
13899
+ -ms-flex: 0 0 var(--kanban-column-width);
13900
+ flex: 0 0 var(--kanban-column-width);
13901
+ -webkit-box-orient: vertical;
13902
+ -webkit-box-direction: normal;
13903
+ -webkit-flex-direction: column;
13904
+ -ms-flex-direction: column;
13905
+ flex-direction: column;
13906
+ min-height: 15rem;
13907
+ padding: 0.75rem;
13908
+ border: 1px solid var(--md-sys-color-outline-variant);
13909
+ border-radius: 0.875rem;
13910
+ background: var(--md-sys-color-surface-variant);
13911
+ -webkit-box-sizing: border-box;
13912
+ box-sizing: border-box;
13913
+ -webkit-transition: border-color 160ms ease, background-color 160ms ease;
13914
+ transition: border-color 160ms ease, background-color 160ms ease;
13915
+ }
13916
+ .kanban-column.is-drag-over {
13917
+ border-color: var(--md-sys-color-primary);
13918
+ background: color-mix(in srgb, var(--md-sys-color-primary-container) 55%, var(--md-sys-color-surface-variant));
13919
+ }
13920
+
13921
+ .kanban-column-header {
13922
+ display: -webkit-box;
13923
+ display: -webkit-flex;
13924
+ display: -ms-flexbox;
13925
+ display: flex;
13926
+ -webkit-box-align: center;
13927
+ -webkit-align-items: center;
13928
+ -ms-flex-align: center;
13929
+ align-items: center;
13930
+ -webkit-box-pack: justify;
13931
+ -webkit-justify-content: space-between;
13932
+ -ms-flex-pack: justify;
13933
+ justify-content: space-between;
13934
+ gap: 0.5rem;
13935
+ padding: 0.25rem 0.25rem 0.75rem;
13936
+ color: var(--md-sys-color-on-surface-variant);
13937
+ }
13938
+
13939
+ .kanban-column-title {
13940
+ margin: 0;
13941
+ font-size: 1rem;
13942
+ font-weight: 600;
13943
+ }
13944
+
13945
+ .kanban-column-count {
13946
+ display: -webkit-inline-box;
13947
+ display: -webkit-inline-flex;
13948
+ display: -ms-inline-flexbox;
13949
+ display: inline-flex;
13950
+ min-width: 1.5rem;
13951
+ min-height: 1.5rem;
13952
+ -webkit-box-align: center;
13953
+ -webkit-align-items: center;
13954
+ -ms-flex-align: center;
13955
+ align-items: center;
13956
+ -webkit-box-pack: center;
13957
+ -webkit-justify-content: center;
13958
+ -ms-flex-pack: center;
13959
+ justify-content: center;
13960
+ padding: 0 0.375rem;
13961
+ border-radius: 999px;
13962
+ background: var(--md-sys-color-surface);
13963
+ color: var(--md-sys-color-on-surface);
13964
+ font-size: 0.75rem;
13965
+ }
13966
+
13967
+ .kanban-column-body {
13968
+ display: -webkit-box;
13969
+ display: -webkit-flex;
13970
+ display: -ms-flexbox;
13971
+ display: flex;
13972
+ -webkit-box-flex: 1;
13973
+ -webkit-flex: 1 1 auto;
13974
+ -ms-flex: 1 1 auto;
13975
+ flex: 1 1 auto;
13976
+ -webkit-box-orient: vertical;
13977
+ -webkit-box-direction: normal;
13978
+ -webkit-flex-direction: column;
13979
+ -ms-flex-direction: column;
13980
+ flex-direction: column;
13981
+ gap: 0.75rem;
13982
+ min-height: 8rem;
13983
+ }
13984
+
13985
+ .kanban-card {
13986
+ position: relative;
13987
+ display: -webkit-box;
13988
+ display: -webkit-flex;
13989
+ display: -ms-flexbox;
13990
+ display: flex;
13991
+ -webkit-box-orient: vertical;
13992
+ -webkit-box-direction: normal;
13993
+ -webkit-flex-direction: column;
13994
+ -ms-flex-direction: column;
13995
+ flex-direction: column;
13996
+ gap: 0.625rem;
13997
+ padding: 1rem;
13998
+ border: 1px solid var(--md-sys-color-outline-variant);
13999
+ border-radius: 0.75rem;
14000
+ background: var(--md-sys-color-surface);
14001
+ color: var(--md-sys-color-on-surface);
14002
+ -webkit-box-shadow: var(--md-sys-elevation-level1, 0 1px 2px rgba(0, 0, 0, 0.14));
14003
+ box-shadow: var(--md-sys-elevation-level1, 0 1px 2px rgba(0, 0, 0, 0.14));
14004
+ cursor: -webkit-grab;
14005
+ cursor: grab;
14006
+ -webkit-user-select: none;
14007
+ -moz-user-select: none;
14008
+ -ms-user-select: none;
14009
+ user-select: none;
14010
+ }
14011
+ .kanban-card:active, .kanban-card.is-dragging {
14012
+ cursor: -webkit-grabbing;
14013
+ cursor: grabbing;
14014
+ }
14015
+ .kanban-card.is-dragging {
14016
+ opacity: 0.45;
14017
+ }
14018
+ .kanban-card:focus-visible {
14019
+ outline: 2px solid var(--md-sys-color-primary);
14020
+ outline-offset: 2px;
14021
+ }
14022
+
14023
+ .kanban-card-title {
14024
+ margin: 0;
14025
+ font-size: 1rem;
14026
+ font-weight: 600;
14027
+ }
14028
+
14029
+ .kanban-card-description {
14030
+ margin: 0;
14031
+ color: var(--md-sys-color-on-surface-variant);
14032
+ font-size: 0.875rem;
14033
+ }
14034
+
14035
+ .kanban-card-meta {
14036
+ display: -webkit-box;
14037
+ display: -webkit-flex;
14038
+ display: -ms-flexbox;
14039
+ display: flex;
14040
+ -webkit-box-align: center;
14041
+ -webkit-align-items: center;
14042
+ -ms-flex-align: center;
14043
+ align-items: center;
14044
+ -webkit-box-pack: justify;
14045
+ -webkit-justify-content: space-between;
14046
+ -ms-flex-pack: justify;
14047
+ justify-content: space-between;
14048
+ gap: 0.5rem;
14049
+ color: var(--md-sys-color-on-surface-variant);
14050
+ font-size: 0.75rem;
14051
+ }
14052
+
14053
+ .kanban-card-label {
14054
+ display: -webkit-inline-box;
14055
+ display: -webkit-inline-flex;
14056
+ display: -ms-inline-flexbox;
14057
+ display: inline-flex;
14058
+ -webkit-box-align: center;
14059
+ -webkit-align-items: center;
14060
+ -ms-flex-align: center;
14061
+ align-items: center;
14062
+ -webkit-align-self: flex-start;
14063
+ -ms-flex-item-align: start;
14064
+ align-self: flex-start;
14065
+ padding: 0.1875rem 0.5rem;
14066
+ border-radius: 999px;
14067
+ background: var(--md-sys-color-secondary-container);
14068
+ color: var(--md-sys-color-on-secondary-container);
14069
+ font-size: 0.75rem;
14070
+ }
14071
+
14072
+ .kanban-empty {
14073
+ display: grid;
14074
+ min-height: 5rem;
14075
+ place-items: center;
14076
+ border: 1px dashed var(--md-sys-color-outline);
14077
+ border-radius: 0.625rem;
14078
+ color: var(--md-sys-color-on-surface-variant);
14079
+ font-size: 0.875rem;
14080
+ }
14081
+
14082
+ @media (max-width: 600px) {
14083
+ .kanban-board {
14084
+ --kanban-column-width: min(20rem, calc(100vw - 3rem));
14085
+ }
14086
+ }
13866
14087
  .collapsible {
13867
14088
  padding-left: 0;
13868
14089
  list-style-type: none;