kmaterialize 2.0.0 → 2.3.3-1.0.0
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/LICENSE +21 -21
- package/README.md +55 -91
- package/components/appbar/_navbar.old.scss +223 -0
- package/components/appbar/appbar.css +235 -0
- package/components/appbar/appbar.mjs +54 -0
- package/components/appbar/appbar.test.mjs +9 -0
- package/components/atomic/atomic.mjs +19 -0
- package/components/atomic/component.mjs +67 -0
- package/components/badge/_badges.scss +75 -0
- package/components/badge/badge.mjs +11 -0
- package/components/breadcrumb/_breadcrumb.scss +27 -0
- package/components/button/_buttons.scss +352 -0
- package/components/button/button.mjs +11 -0
- package/components/button/buttons.stories.ts +150 -0
- package/components/button/buttons.ts +282 -0
- package/components/button/examples.iso.js +18 -0
- package/components/button/fabSpec.js +65 -0
- package/components/button/test-button.mjs +16 -0
- package/components/card/_cards.scss +229 -0
- package/components/card/card.mjs +10 -0
- package/components/card/cards.ts +185 -0
- package/components/card/cardsSpec.js +269 -0
- package/components/carousel/_carousel.scss +93 -0
- package/components/carousel/_slider.scss +119 -0
- package/components/carousel/carousel.ts +693 -0
- package/components/carousel/carouselSpec.js +61 -0
- package/components/carousel/slider.ts +431 -0
- package/components/carousel/sliderSpec.js +99 -0
- package/components/checkbox/_checkboxes.scss +281 -0
- package/components/checkbox/checkbox.mdx +24 -0
- package/components/checkbox/checkbox.stories.ts +120 -0
- package/components/chip/chip.mjs +37 -0
- package/components/chip/chip.test.mjs +10 -0
- package/components/chip/chips.css +126 -0
- package/components/chip/chips.mdx +33 -0
- package/components/chip/chips.ts +440 -0
- package/components/chip/chipsSpec.js +165 -0
- package/components/chip/readme.md +323 -0
- package/components/collapsible/_collapsible.scss +82 -0
- package/components/collapsible/collapsible.ts +230 -0
- package/components/collapsible/collapsibleSpec.js +240 -0
- package/components/datepicker/_datepicker.scss +268 -0
- package/components/datepicker/datepicker.ts +1450 -0
- package/components/datepicker/datepickerSpec.js +162 -0
- package/components/dialog/_materialbox.scss +42 -0
- package/components/dialog/_modal.scss +79 -0
- package/components/dialog/materialbox.ts +446 -0
- package/components/dialog/materialboxSpec.js +37 -0
- package/components/dialog/modal.ts +111 -0
- package/components/dialog/modalSpec.js +83 -0
- package/components/dialog/readme.md +5 -0
- package/components/divider/readme.md +15 -0
- package/components/dropdown/_dropdown.scss +83 -0
- package/components/dropdown/dropdown.ts +653 -0
- package/components/dropdown/dropdownSpec.js +108 -0
- package/components/list/_collection.scss +114 -0
- package/components/list/_list.scss +6 -0
- package/components/list/list.mjs +58 -0
- package/components/list/list.test.mjs +16 -0
- package/components/loading/loading.mjs +26 -0
- package/components/loading/loading.test.mjs +10 -0
- package/components/navigation-bar/_old +582 -0
- package/components/navigation-bar/navigation-bar.mdx +20 -0
- package/components/navigation-bar/navigation-bar.stories.ts +39 -0
- package/components/navigation-drawer/_sidenav.scss +247 -0
- package/components/navigation-drawer/sidenav.ts +546 -0
- package/components/navigation-drawer/sidenavSpec.js +124 -0
- package/components/pagination/_pagination.scss +47 -0
- package/components/progress/_preloader.scss +416 -0
- package/components/radio-button/_radio-buttons.scss +112 -0
- package/components/radio-button/radio-buttons.mdx +28 -0
- package/components/radio-button/radio-buttons.stories.ts +51 -0
- package/components/search/autocomplete.ts +620 -0
- package/components/search/autocompleteSpec.js +321 -0
- package/components/slider/_range.scss +157 -0
- package/components/slider/range.ts +216 -0
- package/components/snackbar/_toast.scss +68 -0
- package/components/snackbar/toastSpec.js +132 -0
- package/components/snackbar/toasts.ts +312 -0
- package/components/switch/_switches.scss +121 -0
- package/components/tabs/_tabs.scss +171 -0
- package/components/tabs/tabs.mjs +31 -0
- package/components/tabs/tabs.ts +358 -0
- package/components/tabs/tabsSpec.js +180 -0
- package/components/textfield/_file-input.scss +41 -0
- package/components/textfield/_input-fields.scss +368 -0
- package/components/textfield/_select.scss +104 -0
- package/components/textfield/formfields.css +311 -0
- package/components/textfield/forms.ts +182 -0
- package/components/textfield/formsSpec.js +142 -0
- package/components/textfield/select.ts +472 -0
- package/components/textfield/selectSpec.js +390 -0
- package/components/textfield/textfield.mjs +29 -0
- package/components/textfield/textfield.test.mjs +10 -0
- package/components/timepicker/_timepicker.scss +362 -0
- package/components/timepicker/timepicker.ts +905 -0
- package/components/tooltip/_tooltip.scss +40 -0
- package/components/tooltip/tooltip.ts +380 -0
- package/components/tooltip/tooltipSpec.js +145 -0
- package/dist/css/materialize.colors.min.css +6 -0
- package/dist/css/materialize.css +7578 -6326
- package/dist/css/materialize.min.css +6 -13
- package/dist/css/materialize.min.css.map +1 -0
- package/dist/js/materialize.cjs.js +8410 -0
- package/dist/js/materialize.d.ts +2625 -0
- package/dist/js/materialize.js +8415 -12374
- package/dist/js/materialize.min.js +6 -6
- package/dist/js/materialize.mjs +8382 -0
- package/package.json +82 -47
- package/sass/_colors.scss +417 -0
- package/sass/_global.scss +490 -0
- package/sass/_grid.scss +172 -0
- package/sass/_table_of_contents.scss +28 -0
- package/sass/_tapTarget.scss +102 -0
- package/sass/_typography.scss +58 -0
- package/sass/_variables.scss +56 -0
- package/sass/materialize.scss +51 -0
- package/sass/mixins.module.scss +168 -0
- package/extras/noUiSlider/nouislider.css +0 -406
- package/extras/noUiSlider/nouislider.js +0 -2147
- package/extras/noUiSlider/nouislider.min.js +0 -1
- package/js/anime.min.js +0 -34
- package/js/autocomplete.js +0 -448
- package/js/buttons.js +0 -354
- package/js/cards.js +0 -40
- package/js/carousel.js +0 -717
- package/js/cash.js +0 -960
- package/js/characterCounter.js +0 -136
- package/js/chips.js +0 -481
- package/js/collapsible.js +0 -275
- package/js/component.js +0 -44
- package/js/datepicker.js +0 -975
- package/js/dropdown.js +0 -635
- package/js/forms.js +0 -275
- package/js/global.js +0 -444
- package/js/materialbox.js +0 -453
- package/js/modal.js +0 -382
- package/js/parallax.js +0 -138
- package/js/pushpin.js +0 -148
- package/js/range.js +0 -263
- package/js/scrollspy.js +0 -295
- package/js/select.js +0 -450
- package/js/sidenav.js +0 -580
- package/js/slider.js +0 -359
- package/js/tabs.js +0 -402
- package/js/tapTarget.js +0 -314
- package/js/timepicker.js +0 -647
- package/js/toasts.js +0 -310
- package/js/tooltip.js +0 -303
- package/js/waves.js +0 -335
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { Utils } from '../../src/utils';
|
|
2
|
+
import { Autocomplete, AutocompleteOptions } from '../search/autocomplete';
|
|
3
|
+
import { Component, BaseOptions, InitElements, MElement } from '../../src/component';
|
|
4
|
+
|
|
5
|
+
interface ChipData {
|
|
6
|
+
/** Unique identifier. */
|
|
7
|
+
id: number | string;
|
|
8
|
+
/** Chip text. If not specified, "id" will be used. */
|
|
9
|
+
text?: string;
|
|
10
|
+
/** Chip image (URL). */
|
|
11
|
+
image?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ChipsOptions extends BaseOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Set the chip data.
|
|
17
|
+
* @default []
|
|
18
|
+
*/
|
|
19
|
+
data: ChipData[];
|
|
20
|
+
/**
|
|
21
|
+
* Set first placeholder when there are no tags.
|
|
22
|
+
* @default ""
|
|
23
|
+
*/
|
|
24
|
+
placeholder: string;
|
|
25
|
+
/**
|
|
26
|
+
* Set second placeholder when adding additional tags.
|
|
27
|
+
* @default ""
|
|
28
|
+
*/
|
|
29
|
+
secondaryPlaceholder: string;
|
|
30
|
+
/**
|
|
31
|
+
* Set autocomplete options.
|
|
32
|
+
* @default {}
|
|
33
|
+
*/
|
|
34
|
+
autocompleteOptions: Partial<AutocompleteOptions>;
|
|
35
|
+
/**
|
|
36
|
+
* Toggles abililty to add custom value not in autocomplete list.
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
autocompleteOnly: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Set chips limit.
|
|
42
|
+
* @default Infinity
|
|
43
|
+
*/
|
|
44
|
+
limit: number;
|
|
45
|
+
/**
|
|
46
|
+
* Specifies class to be used in "close" button (useful when working with Material Symbols icon set).
|
|
47
|
+
* @default 'material-icons'
|
|
48
|
+
*/
|
|
49
|
+
closeIconClass: string;
|
|
50
|
+
/**
|
|
51
|
+
* Specifies option to render user input field
|
|
52
|
+
* @default false;
|
|
53
|
+
*/
|
|
54
|
+
allowUserInput: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Callback for chip add.
|
|
57
|
+
* @default null
|
|
58
|
+
*/
|
|
59
|
+
onChipAdd: (element: HTMLElement, chip: HTMLElement) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Callback for chip select.
|
|
62
|
+
* @default null
|
|
63
|
+
*/
|
|
64
|
+
onChipSelect: (element: HTMLElement, chip: HTMLElement) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Callback for chip delete.
|
|
67
|
+
* @default null
|
|
68
|
+
*/
|
|
69
|
+
onChipDelete: (element: HTMLElement, chip: HTMLElement) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const _defaults: ChipsOptions = {
|
|
73
|
+
data: [],
|
|
74
|
+
placeholder: '',
|
|
75
|
+
secondaryPlaceholder: '',
|
|
76
|
+
closeIconClass: 'material-icons',
|
|
77
|
+
autocompleteOptions: {},
|
|
78
|
+
autocompleteOnly: false,
|
|
79
|
+
limit: Infinity,
|
|
80
|
+
allowUserInput: false,
|
|
81
|
+
onChipAdd: null,
|
|
82
|
+
onChipSelect: null,
|
|
83
|
+
onChipDelete: null
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function gGetIndex(el: HTMLElement): number {
|
|
87
|
+
return [...el.parentNode.children].indexOf(el);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
class Chips extends Component<ChipsOptions> {
|
|
91
|
+
/** Array of the current chips data. */
|
|
92
|
+
chipsData: ChipData[];
|
|
93
|
+
/** If the chips has autocomplete enabled. */
|
|
94
|
+
hasAutocomplete: boolean;
|
|
95
|
+
/** Autocomplete instance, if any. */
|
|
96
|
+
autocomplete: Autocomplete;
|
|
97
|
+
#input: HTMLInputElement;
|
|
98
|
+
#label: HTMLLabelElement;
|
|
99
|
+
#chips: HTMLElement[];
|
|
100
|
+
static #keydown: boolean;
|
|
101
|
+
#selectedChip: HTMLElement;
|
|
102
|
+
|
|
103
|
+
constructor(el: HTMLElement, options: Partial<ChipsOptions>) {
|
|
104
|
+
super(el, options, Chips);
|
|
105
|
+
this.el['M_Chips'] = this;
|
|
106
|
+
this.options = {
|
|
107
|
+
...Chips.defaults,
|
|
108
|
+
...options
|
|
109
|
+
};
|
|
110
|
+
this.el.classList.add('chips');
|
|
111
|
+
this.chipsData = [];
|
|
112
|
+
this.#chips = [];
|
|
113
|
+
// Render initial chips
|
|
114
|
+
if (this.options.data.length) {
|
|
115
|
+
this.chipsData = this.options.data;
|
|
116
|
+
this.#renderChips();
|
|
117
|
+
}
|
|
118
|
+
// Render input element, setup event handlers
|
|
119
|
+
if (this.options.allowUserInput) {
|
|
120
|
+
this.#setupLabel();
|
|
121
|
+
this.el.classList.add('input-field');
|
|
122
|
+
this.#setupInput();
|
|
123
|
+
this.#setupEventHandlers();
|
|
124
|
+
// move input to end
|
|
125
|
+
this.el.append(this.#input);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static get defaults() {
|
|
130
|
+
return _defaults;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Initializes instance of Chips.
|
|
135
|
+
* @param el HTML element.
|
|
136
|
+
* @param options Component options.
|
|
137
|
+
*/
|
|
138
|
+
static init(el: HTMLElement, options?: Partial<ChipsOptions>): Chips;
|
|
139
|
+
/**
|
|
140
|
+
* Initializes instances of Chips.
|
|
141
|
+
* @param els HTML elements.
|
|
142
|
+
* @param options Component options.
|
|
143
|
+
*/
|
|
144
|
+
static init(els: InitElements<MElement>, options?: Partial<ChipsOptions>): Chips[];
|
|
145
|
+
/**
|
|
146
|
+
* Initializes instances of Chips.
|
|
147
|
+
* @param els HTML elements.
|
|
148
|
+
* @param options Component options.
|
|
149
|
+
*/
|
|
150
|
+
static init(
|
|
151
|
+
els: HTMLElement | InitElements<MElement>,
|
|
152
|
+
options: Partial<ChipsOptions> = {}
|
|
153
|
+
): Chips | Chips[] {
|
|
154
|
+
return super.init(els, options, Chips);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static getInstance(el: HTMLElement): Chips {
|
|
158
|
+
return el['M_Chips'];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getData() {
|
|
162
|
+
return this.chipsData;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
destroy() {
|
|
166
|
+
if (this.options.allowUserInput) this.#removeEventHandlers();
|
|
167
|
+
this.#chips.forEach((c) => c.remove());
|
|
168
|
+
this.#chips = [];
|
|
169
|
+
this.el['M_Chips'] = undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#setupEventHandlers() {
|
|
173
|
+
this.el.addEventListener('click', this.#handleChipClick);
|
|
174
|
+
// @todo why do we need this as document event listener, shouldn't we apply it to the element wrapper itself?
|
|
175
|
+
document.addEventListener('keydown', Chips.#handleChipsKeydown);
|
|
176
|
+
document.addEventListener('keyup', Chips.#handleChipsKeyup);
|
|
177
|
+
this.el.addEventListener('blur', Chips.#handleChipsBlur, true);
|
|
178
|
+
this.#input.addEventListener('focus', this.#handleInputFocus);
|
|
179
|
+
this.#input.addEventListener('blur', this.#handleInputBlur);
|
|
180
|
+
this.#input.addEventListener('keydown', this.#handleInputKeydown);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#removeEventHandlers() {
|
|
184
|
+
this.el.removeEventListener('click', this.#handleChipClick);
|
|
185
|
+
document.removeEventListener('keydown', Chips.#handleChipsKeydown);
|
|
186
|
+
document.removeEventListener('keyup', Chips.#handleChipsKeyup);
|
|
187
|
+
this.el.removeEventListener('blur', Chips.#handleChipsBlur, true);
|
|
188
|
+
this.#input.removeEventListener('focus', this.#handleInputFocus);
|
|
189
|
+
this.#input.removeEventListener('blur', this.#handleInputBlur);
|
|
190
|
+
this.#input.removeEventListener('keydown', this.#handleInputKeydown);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#handleChipClick = (e: MouseEvent) => {
|
|
194
|
+
const _chip = (<HTMLElement>e.target).closest('.chip');
|
|
195
|
+
const clickedClose = (<HTMLElement>e.target).classList.contains('close');
|
|
196
|
+
if (_chip) {
|
|
197
|
+
const index = [..._chip.parentNode.children].indexOf(_chip);
|
|
198
|
+
if (clickedClose) {
|
|
199
|
+
this.deleteChip(index);
|
|
200
|
+
this.#input.focus();
|
|
201
|
+
} else {
|
|
202
|
+
this.selectChip(index);
|
|
203
|
+
}
|
|
204
|
+
// Default handle click to focus on input
|
|
205
|
+
} else {
|
|
206
|
+
this.#input.focus();
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
static #handleChipsKeydown(e: KeyboardEvent) {
|
|
211
|
+
Chips.#keydown = true;
|
|
212
|
+
const chips = (<HTMLElement>e.target).closest('.chips');
|
|
213
|
+
const chipsKeydown = e.target && chips;
|
|
214
|
+
|
|
215
|
+
// Don't handle keydown inputs on input and textarea
|
|
216
|
+
const tag = (<HTMLElement>e.target).tagName;
|
|
217
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA' || !chipsKeydown) return;
|
|
218
|
+
|
|
219
|
+
const currChips: Chips = chips['M_Chips'];
|
|
220
|
+
|
|
221
|
+
if (Utils.keys.BACKSPACE.includes(e.key) || Utils.keys.DELETE.includes(e.key)) {
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
let selectIndex = currChips.chipsData.length;
|
|
224
|
+
if (currChips.#selectedChip) {
|
|
225
|
+
const index = gGetIndex(currChips.#selectedChip);
|
|
226
|
+
currChips.deleteChip(index);
|
|
227
|
+
currChips.#selectedChip = null;
|
|
228
|
+
// Make sure selectIndex doesn't go negative
|
|
229
|
+
selectIndex = Math.max(index - 1, 0);
|
|
230
|
+
}
|
|
231
|
+
if (currChips.chipsData.length) currChips.selectChip(selectIndex);
|
|
232
|
+
else currChips.#input.focus();
|
|
233
|
+
} else if (Utils.keys.ARROW_LEFT.includes(e.key)) {
|
|
234
|
+
if (currChips.#selectedChip) {
|
|
235
|
+
const selectIndex = gGetIndex(currChips.#selectedChip) - 1;
|
|
236
|
+
if (selectIndex < 0) return;
|
|
237
|
+
currChips.selectChip(selectIndex);
|
|
238
|
+
}
|
|
239
|
+
} else if (Utils.keys.ARROW_RIGHT.includes(e.key)) {
|
|
240
|
+
if (currChips.#selectedChip) {
|
|
241
|
+
const selectIndex = gGetIndex(currChips.#selectedChip) + 1;
|
|
242
|
+
if (selectIndex >= currChips.chipsData.length) currChips.#input.focus();
|
|
243
|
+
else currChips.selectChip(selectIndex);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static #handleChipsKeyup() {
|
|
249
|
+
Chips.#keydown = false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static #handleChipsBlur(e: Event) {
|
|
253
|
+
if (!Chips.#keydown && document.hidden) {
|
|
254
|
+
const chips = (<HTMLElement>e.target).closest('.chips');
|
|
255
|
+
const currChips: Chips = chips['M_Chips'];
|
|
256
|
+
currChips.#selectedChip = null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#handleInputFocus = () => {
|
|
261
|
+
this.el.classList.add('focus');
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
#handleInputBlur = () => {
|
|
265
|
+
this.el.classList.remove('focus');
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
#handleInputKeydown = (e: KeyboardEvent) => {
|
|
269
|
+
Chips.#keydown = true;
|
|
270
|
+
if (Utils.keys.ENTER.includes(e.key)) {
|
|
271
|
+
// Override enter if autocompleting.
|
|
272
|
+
if (this.hasAutocomplete && this.autocomplete && this.autocomplete.isOpen) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
e.preventDefault();
|
|
276
|
+
if (!this.hasAutocomplete || (this.hasAutocomplete && !this.options.autocompleteOnly)) {
|
|
277
|
+
this.addChip({ id: this.#input.value });
|
|
278
|
+
}
|
|
279
|
+
this.#input.value = '';
|
|
280
|
+
} else if (
|
|
281
|
+
(Utils.keys.BACKSPACE.includes(e.key) || Utils.keys.ARROW_LEFT.includes(e.key)) &&
|
|
282
|
+
this.#input.value === '' &&
|
|
283
|
+
this.chipsData.length
|
|
284
|
+
) {
|
|
285
|
+
e.preventDefault();
|
|
286
|
+
this.selectChip(this.chipsData.length - 1);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
#renderChip(chip: ChipData): HTMLLIElement {
|
|
291
|
+
if (!chip.id) return;
|
|
292
|
+
const renderedChip = document.createElement('li');
|
|
293
|
+
renderedChip.classList.add('chip');
|
|
294
|
+
renderedChip.innerText = chip.text || <string>chip.id;
|
|
295
|
+
// attach image if needed
|
|
296
|
+
if (chip.image) {
|
|
297
|
+
const img = document.createElement('img');
|
|
298
|
+
img.setAttribute('src', chip.image);
|
|
299
|
+
renderedChip.insertBefore(img, renderedChip.firstChild);
|
|
300
|
+
}
|
|
301
|
+
if (this.options.allowUserInput) {
|
|
302
|
+
const closeButton = document.createElement('button');
|
|
303
|
+
closeButton.classList.add(this.options.closeIconClass, 'close');
|
|
304
|
+
closeButton.innerText = 'close';
|
|
305
|
+
renderedChip.appendChild(closeButton);
|
|
306
|
+
}
|
|
307
|
+
return renderedChip;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#renderChips() {
|
|
311
|
+
this.#chips = []; //.remove();
|
|
312
|
+
for (let i = 0; i < this.chipsData.length; i++) {
|
|
313
|
+
const chipElem = this.#renderChip(this.chipsData[i]);
|
|
314
|
+
this.el.appendChild(chipElem);
|
|
315
|
+
this.#chips.push(chipElem);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
#setupAutocomplete() {
|
|
320
|
+
this.options.autocompleteOptions.onAutocomplete = (items) => {
|
|
321
|
+
if (items.length > 0)
|
|
322
|
+
this.addChip({
|
|
323
|
+
id: items[0].id,
|
|
324
|
+
text: items[0].text,
|
|
325
|
+
image: items[0].image
|
|
326
|
+
});
|
|
327
|
+
this.#input.value = '';
|
|
328
|
+
this.#input.focus();
|
|
329
|
+
};
|
|
330
|
+
this.autocomplete = Autocomplete.init(this.#input, this.options.autocompleteOptions);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#setupInput() {
|
|
334
|
+
this.#input = this.el.querySelector('input');
|
|
335
|
+
if (!this.#input) {
|
|
336
|
+
this.#input = document.createElement('input');
|
|
337
|
+
this.el.append(this.#input);
|
|
338
|
+
}
|
|
339
|
+
this.#input.classList.add('input');
|
|
340
|
+
this.hasAutocomplete = Object.keys(this.options.autocompleteOptions).length > 0;
|
|
341
|
+
// Setup autocomplete if needed
|
|
342
|
+
if (this.hasAutocomplete) this.#setupAutocomplete();
|
|
343
|
+
this.#setPlaceholder();
|
|
344
|
+
// Set input id
|
|
345
|
+
if (!this.#input.getAttribute('id')) this.#input.setAttribute('id', Utils.guid());
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
#setupLabel() {
|
|
349
|
+
this.#label = this.el.querySelector('label');
|
|
350
|
+
if (this.#label) this.#label.setAttribute('for', this.#input.getAttribute('id'));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
#setPlaceholder() {
|
|
354
|
+
if (this.chipsData !== undefined && !this.chipsData.length && this.options.placeholder) {
|
|
355
|
+
this.#input.placeholder = this.options.placeholder;
|
|
356
|
+
} else if (
|
|
357
|
+
(this.chipsData === undefined || !!this.chipsData.length) &&
|
|
358
|
+
this.options.secondaryPlaceholder
|
|
359
|
+
) {
|
|
360
|
+
this.#input.placeholder = this.options.secondaryPlaceholder;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
#isValidAndNotExist(chip: ChipData) {
|
|
365
|
+
const isValid = !!chip.id;
|
|
366
|
+
const doesNotExist = !this.chipsData.some((item) => item.id == chip.id);
|
|
367
|
+
return isValid && doesNotExist;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Add chip to input.
|
|
372
|
+
* @param chip Chip data object
|
|
373
|
+
*/
|
|
374
|
+
addChip(chip: ChipData) {
|
|
375
|
+
if (!this.#isValidAndNotExist(chip) || this.chipsData.length >= this.options.limit) return;
|
|
376
|
+
const renderedChip = this.#renderChip(chip);
|
|
377
|
+
this.#chips.push(renderedChip);
|
|
378
|
+
this.chipsData.push(chip);
|
|
379
|
+
//$(this._input).before(renderedChip);
|
|
380
|
+
this.#input.before(renderedChip);
|
|
381
|
+
this.#setPlaceholder();
|
|
382
|
+
// fire chipAdd callback
|
|
383
|
+
if (typeof this.options.onChipAdd === 'function') {
|
|
384
|
+
this.options.onChipAdd(this.el, renderedChip);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Delete nth chip.
|
|
390
|
+
* @param chipIndex Index of chip
|
|
391
|
+
*/
|
|
392
|
+
deleteChip(chipIndex: number) {
|
|
393
|
+
const chip = this.#chips[chipIndex];
|
|
394
|
+
this.#chips[chipIndex].remove();
|
|
395
|
+
this.#chips.splice(chipIndex, 1);
|
|
396
|
+
this.chipsData.splice(chipIndex, 1);
|
|
397
|
+
this.#setPlaceholder();
|
|
398
|
+
// fire chipDelete callback
|
|
399
|
+
if (typeof this.options.onChipDelete === 'function') {
|
|
400
|
+
this.options.onChipDelete(this.el, chip);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Select nth chip.
|
|
406
|
+
* @param chipIndex Index of chip
|
|
407
|
+
*/
|
|
408
|
+
selectChip(chipIndex: number) {
|
|
409
|
+
const chip = this.#chips[chipIndex];
|
|
410
|
+
this.#selectedChip = chip;
|
|
411
|
+
chip.focus();
|
|
412
|
+
// fire chipSelect callback
|
|
413
|
+
if (typeof this.options.onChipSelect === 'function') {
|
|
414
|
+
this.options.onChipSelect(this.el, chip);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
static Init() {
|
|
419
|
+
if (typeof document !== 'undefined')
|
|
420
|
+
// Handle removal of static chips.
|
|
421
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
422
|
+
const chips = document.querySelectorAll('.chips');
|
|
423
|
+
chips.forEach((el) => {
|
|
424
|
+
// if (el && (el['M_Chips == undefined) return;
|
|
425
|
+
el.addEventListener('click', (e) => {
|
|
426
|
+
if ((<HTMLElement>e.target).classList.contains('close')) {
|
|
427
|
+
const chip = (<HTMLElement>e.target).closest('.chip');
|
|
428
|
+
if (chip) chip.remove();
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
static {
|
|
436
|
+
this.#keydown = false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export { Chips, ChipData, ChipsOptions };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
describe('Chips', () => {
|
|
2
|
+
const fixture = `<div class="chips"></div>
|
|
3
|
+
<div class="chips chips-initial"></div>
|
|
4
|
+
<div class="chips input-field"><input></div>
|
|
5
|
+
<div class="chips chips-initial input-field"><input></div>
|
|
6
|
+
<div class="chips chips-placeholder input-field"><input></div>
|
|
7
|
+
<div class="chips chips-autocomplete input-field"><input></div>`;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
XloadHtml(fixture);
|
|
11
|
+
M.Chips.init(document.querySelector('.chips'));
|
|
12
|
+
M.Chips.init(document.querySelector('.chips-initial'), {
|
|
13
|
+
data: [
|
|
14
|
+
{ id: 12, text: 'Apple' },
|
|
15
|
+
{ id: 13, text: 'Microsoft' },
|
|
16
|
+
{
|
|
17
|
+
id: 42,
|
|
18
|
+
text: 'Google',
|
|
19
|
+
image:
|
|
20
|
+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
});
|
|
24
|
+
M.Chips.init(document.querySelector('.chips.input-field'), {
|
|
25
|
+
allowUserInput: true
|
|
26
|
+
});
|
|
27
|
+
M.Chips.init(document.querySelector('.chips-initial.input-field'), {
|
|
28
|
+
allowUserInput: true,
|
|
29
|
+
data: [
|
|
30
|
+
{ id: 12, text: 'Apple' },
|
|
31
|
+
{ id: 13, text: 'Microsoft' },
|
|
32
|
+
{
|
|
33
|
+
id: 42,
|
|
34
|
+
text: 'Google',
|
|
35
|
+
image:
|
|
36
|
+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
M.Chips.init(document.querySelector('.chips-placeholder.input-field'), {
|
|
41
|
+
allowUserInput: true,
|
|
42
|
+
placeholder: 'Enter a tag',
|
|
43
|
+
secondaryPlaceholder: '+Tag'
|
|
44
|
+
});
|
|
45
|
+
M.Chips.init(document.querySelector('.chips-autocomplete.input-field'), {
|
|
46
|
+
allowUserInput: true,
|
|
47
|
+
autocompleteOptions: {
|
|
48
|
+
data: [
|
|
49
|
+
{ id: 12, text: 'Apple' },
|
|
50
|
+
{ id: 13, text: 'Microsoft' },
|
|
51
|
+
{ id: 42, text: 'Google' }
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
afterEach(() => XunloadFixtures());
|
|
57
|
+
|
|
58
|
+
describe('chips plugin', () => {
|
|
59
|
+
let chips, chipsUserInput, input;
|
|
60
|
+
|
|
61
|
+
it('should work with multiple initializations', () => {
|
|
62
|
+
chips = document.querySelector('.chips');
|
|
63
|
+
M.Chips.init(chips);
|
|
64
|
+
M.Chips.init(chips);
|
|
65
|
+
M.Chips.init(chips);
|
|
66
|
+
chipsUserInput = document.querySelector('.chips.input-field');
|
|
67
|
+
M.Chips.init(chips, { allowUserInput: true });
|
|
68
|
+
input = chipsUserInput.querySelectorAll('input');
|
|
69
|
+
expect(input.length).toEqual(1, 'Should dynamically generate chips structure.');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should be able to add chip', (done) => {
|
|
73
|
+
chips = document.querySelector('.chips.input-field');
|
|
74
|
+
input = chips.querySelector('input');
|
|
75
|
+
input.value = 'one';
|
|
76
|
+
keydown(input, 13);
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
const numChips = chips.querySelectorAll('.chip').length;
|
|
79
|
+
const oneChip = chips.querySelector('.chip');
|
|
80
|
+
expect(numChips).toEqual(1, 'one chip should have been added');
|
|
81
|
+
for (let i = oneChip.children.length - 1; i >= 0; i--) {
|
|
82
|
+
oneChip.children[i].remove();
|
|
83
|
+
}
|
|
84
|
+
expect(oneChip.innerText).toEqual('one', 'the chip should have value "one"');
|
|
85
|
+
done();
|
|
86
|
+
}, 100); // 400
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should be able to delete chip', (done) => {
|
|
90
|
+
chips = document.querySelector('.chips.chips-initial.input-field');
|
|
91
|
+
let numChips = chips.querySelectorAll('.chip').length;
|
|
92
|
+
expect(numChips).toEqual(3, '3 initial chips should have been added');
|
|
93
|
+
const chipCloseButton = chips.querySelectorAll('.chip .close');
|
|
94
|
+
expect(chipCloseButton.length).toEqual(3, 'expected all chips to have close button');
|
|
95
|
+
click(chipCloseButton[0]);
|
|
96
|
+
setTimeout(() => {
|
|
97
|
+
numChips = chips.querySelectorAll('.chip').length;
|
|
98
|
+
expect(numChips).toEqual(2, 'one chip should have been deleted');
|
|
99
|
+
done();
|
|
100
|
+
}, 100); // 400
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should have working callbacks', (done) => {
|
|
104
|
+
chips = document.querySelector('.chips.input-field');
|
|
105
|
+
let chipWasAdded = false;
|
|
106
|
+
let chipAddedElem = null;
|
|
107
|
+
let chipSelect = false;
|
|
108
|
+
let chipSelected = null;
|
|
109
|
+
let chipDelete = false;
|
|
110
|
+
let chipDeleted = null;
|
|
111
|
+
|
|
112
|
+
M.Chips.init(chips, {
|
|
113
|
+
allowUserInput: true,
|
|
114
|
+
data: [{ id: 'One' }, { id: 'Two' }, { id: 'Three' }],
|
|
115
|
+
onChipAdd: (chipsEl, chipEl) => {
|
|
116
|
+
chipAddedElem = chipEl;
|
|
117
|
+
chipWasAdded = true;
|
|
118
|
+
},
|
|
119
|
+
onChipSelect: (chipsEl, chipEl) => {
|
|
120
|
+
chipSelected = chipEl;
|
|
121
|
+
chipSelect = true;
|
|
122
|
+
},
|
|
123
|
+
onChipDelete: (chipsEl, chipEl) => {
|
|
124
|
+
chipDeleted = chipEl;
|
|
125
|
+
chipDelete = true;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
input = chips.querySelector('input');
|
|
130
|
+
input.value = 'Four';
|
|
131
|
+
expect(chipWasAdded).toEqual(false, 'callback not yet fired');
|
|
132
|
+
expect(chipSelect).toEqual(false, 'callback not yet fired');
|
|
133
|
+
expect(chipDelete).toEqual(false, 'callback not yet fired');
|
|
134
|
+
keydown(input, 13);
|
|
135
|
+
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
expect(chipWasAdded).toEqual(true, 'add callback fired');
|
|
138
|
+
expect(chipAddedElem.childNodes[0].nodeValue).toEqual(
|
|
139
|
+
'Four',
|
|
140
|
+
'add callback provides correct chip element'
|
|
141
|
+
);
|
|
142
|
+
click(chips.querySelectorAll('.chip')[1]);
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
expect(chipSelect).toEqual(true, 'select callback fired');
|
|
145
|
+
expect(chipSelected.childNodes[0].nodeValue).toEqual(
|
|
146
|
+
'Two',
|
|
147
|
+
'select callback provides correct chip element'
|
|
148
|
+
);
|
|
149
|
+
click(chips.querySelectorAll('.close')[2]);
|
|
150
|
+
setTimeout(() => {
|
|
151
|
+
expect(chipDelete).toEqual(true, 'delete callback fired');
|
|
152
|
+
expect(chipDeleted.childNodes[0].nodeValue).toEqual(
|
|
153
|
+
'Three',
|
|
154
|
+
'add callback provides correct chip element'
|
|
155
|
+
);
|
|
156
|
+
done();
|
|
157
|
+
}, 100);
|
|
158
|
+
}, 100);
|
|
159
|
+
}, 100);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// describe("Chips autocomplete", () => {
|
|
164
|
+
// });
|
|
165
|
+
});
|