Describe the Bug
In the Chips component (js/chips.js), keyboard navigation (pressing Arrow keys or Backspace on an empty input) causes an uncaught runtime error:
TypeError: Cannot read property 'focus' of undefined (or Cannot read properties of undefined (reading 'focus')).
Additionally, calling .destroy() on a Chips instance fails to remove the rendered chips from the DOM.
Root Cause Analysis
In Cash (and jQuery), the .add() method is immutable—it does not mutate the existing collection in-place; instead, it returns a new collection instance.
- In
js/chips.js:49, this.$chips is initialized to an empty Cash collection:
- When chips are rendered or added in
_renderChips() (line 323) and addChip() (line 411):
// Line 323:
this.$chips.add(chipEl); // Return value is discarded!
// Line 411:
this.$chips.add(renderedChip); // Return value is discarded!
- Because the returned collections are never assigned back to
this.$chips, this.$chips remains an empty collection (length === 0).
- When
selectChip(chipIndex) is triggered during keyboard navigation:
selectChip(chipIndex) {
let $chip = this.$chips.eq(chipIndex); // Empty collection
this._selectedChip = $chip;
$chip[0].focus(); // CRASH: $chip[0] is undefined
}
- In
destroy(), this.$chips.remove() does nothing because this.$chips.length === 0, leaving orphaned elements in the DOM.
Steps to Reproduce
- Initialize a Chips component with initial data:
const elem = document.querySelector('.chips');
const instance = M.Chips.init(elem, {
data: [{ tag: 'Apple' }, { tag: 'Microsoft' }, { tag: 'Google' }]
});
- Focus the input field and press
Backspace or Left Arrow key (or call instance.selectChip(0) programmatically).
- Check the browser console.
Expected Behavior
- The selected chip receives focus and enables chip keyboard navigation.
- Calling
instance.destroy() cleans up and removes all generated chip DOM elements.
Actual Behavior
- Uncaught
TypeError: Cannot read property 'focus' of undefined.
instance.destroy() leaves all chip elements rendered in the DOM.
Proposed Fix
Assign the return value of .add() back to this.$chips:
--- a/js/chips.js
+++ b/js/chips.js
@@ -320,7 +320,7 @@
this.$chips.remove();
for (let i = 0; i < this.chipsData.length; i++) {
let chipEl = this._renderChip(this.chipsData[i]);
this.$el.append(chipEl);
- this.$chips.add(chipEl);
+ this.$chips = this.$chips.add(chipEl);
}
// move input to end
@@ -408,7 +408,7 @@
}
let renderedChip = this._renderChip(chip);
- this.$chips.add(renderedChip);
+ this.$chips = this.$chips.add(renderedChip);
this.chipsData.push(chip);
$(this.$input).before(renderedChip);
this._setPlaceholder();
Additional Bonus Findings
-
js/dropdown.js (Line 389) — Uncaught TypeError in _getDropdownPosition():
Inside _getDropdownPosition(), this.el.offsetParent.getBoundingClientRect() is executed. When the dropdown trigger is placed inside an element with position: fixed (such as a fixed navbar .navbar-fixed), this.el.offsetParent is null per W3C specification, causing an uncaught TypeError on open. Furthermore, offsetParentBRect is an unused variable.
-
js/carousel.js (Lines 527–530) — Invalid NaNpx CSS Transform:
In _scroll(), variable i is declared but uninitialized (undefined). Inside the first center item calculation block, translateX(${dir * this.options.shift * tween * i}px) evaluates to NaN, setting translateX(NaNpx) and causing the browser to reject the CSS transform.
-
js/characterCounter.js (Line 54) — Instance Reference Leak in destroy():
destroy() executes this.el.CharacterCounter = undefined; (missing the M_ prefix), leaving this.el.M_CharacterCounter intact and causing M.CharacterCounter.getInstance(el) to return the destroyed instance.
Describe the Bug
In the
Chipscomponent (js/chips.js), keyboard navigation (pressing Arrow keys or Backspace on an empty input) causes an uncaught runtime error:TypeError: Cannot read property 'focus' of undefined(orCannot read properties of undefined (reading 'focus')).Additionally, calling
.destroy()on a Chips instance fails to remove the rendered chips from the DOM.Root Cause Analysis
In Cash (and jQuery), the
.add()method is immutable—it does not mutate the existing collection in-place; instead, it returns a new collection instance.js/chips.js:49,this.$chipsis initialized to an empty Cash collection:_renderChips()(line 323) andaddChip()(line 411):this.$chips,this.$chipsremains an empty collection (length === 0).selectChip(chipIndex)is triggered during keyboard navigation:destroy(),this.$chips.remove()does nothing becausethis.$chips.length === 0, leaving orphaned elements in the DOM.Steps to Reproduce
BackspaceorLeft Arrowkey (or callinstance.selectChip(0)programmatically).Expected Behavior
instance.destroy()cleans up and removes all generated chip DOM elements.Actual Behavior
TypeError: Cannot read property 'focus' of undefined.instance.destroy()leaves all chip elements rendered in the DOM.Proposed Fix
Assign the return value of
.add()back tothis.$chips:Additional Bonus Findings
js/dropdown.js(Line 389) — UncaughtTypeErrorin_getDropdownPosition():Inside
_getDropdownPosition(),this.el.offsetParent.getBoundingClientRect()is executed. When the dropdown trigger is placed inside an element withposition: fixed(such as a fixed navbar.navbar-fixed),this.el.offsetParentisnullper W3C specification, causing an uncaughtTypeErroron open. Furthermore,offsetParentBRectis an unused variable.js/carousel.js(Lines 527–530) — InvalidNaNpxCSS Transform:In
_scroll(), variableiis declared but uninitialized (undefined). Inside the first center item calculation block,translateX(${dir * this.options.shift * tween * i}px)evaluates toNaN, settingtranslateX(NaNpx)and causing the browser to reject the CSS transform.js/characterCounter.js(Line 54) — Instance Reference Leak indestroy():destroy()executesthis.el.CharacterCounter = undefined;(missing theM_prefix), leavingthis.el.M_CharacterCounterintact and causingM.CharacterCounter.getInstance(el)to return the destroyed instance.