Dogfalo / materialize

Materialize, a CSS Framework based on Material Design
https://materializecss.com
MIT License
38.86k stars 4.74k forks source link

Clock displays and hides immediately after updating Chrome version to 73 #6312

Open subashdbc opened 5 years ago

subashdbc commented 5 years ago

Hi,

When we click on the filed the clock modal opens and immediately close https://codepen.io/anon/pen/evQxPy This happens after I have updated my chrome version to 73

subashdbc commented 5 years ago

Any quick fix on this?

swforeman1978 commented 5 years ago

How to fix this error?

luizpinheirodev commented 5 years ago

The same here!! :( This problem happens only in Chrome, Edge works fine.

Marzon commented 5 years ago

It´s a race condition

https://bugs.chromium.org/p/chromium/issues/detail?id=941910

The workaround is a setTimeout on root focus.

DanielRuf commented 5 years ago

Exactly. Either throttle / debounce (see pickadate 3.6.1) or use the setTimeout workaround.

subashdbc commented 5 years ago

@Marzon We use materialize.js and where to use setTimeout?

DavinderPRO commented 5 years ago

I am also looking for workaround for time picker cause upgrading to new materialcss is time consuming. Please let us know workaround for time picker

For datepicker comment this code in materialize.js file P.close(target === P.$root.children()[0]);

DavinderPRO commented 5 years ago

It´s a race condition

https://bugs.chromium.org/p/chromium/issues/detail?id=941910

The workaround is a setTimeout on root focus.

Can please specify where to add setTimeout in the JS file. That would be really helpful

DanielRuf commented 5 years ago

See https://github.com/amsul/pickadate.js/pull/1140 with the right workaround.

The other proposed change breaks focus (a11y).

subashdbc commented 5 years ago

@DanielRuf it's quite hard to find the places to change the logic based on github.com/amsul/pickadate.js/pull/1140/files these changes, on materialize.js file. It would much be appreciated any solution on materialize.js

Marzon commented 5 years ago

Hi! On materialize.js, add a setTimeout (100 ms) in end of the

  open: function (dontGiveFocus) {

Move de $document.on('click.' . .... and the

P.$root.eq(0).focus();

to the setTimeOut function.

Leave document.on first and $root.focus() after document.on ..

I´m waiting the race condition bug to be fixed.. before to propose a PR..

subashdbc commented 5 years ago

Hi, Not sure, if this is a bulletproof solution but this works for me especially with clockpicker. The version should be materializecss (v0.100.2). In materialize.js file Line No: 8913

ClockPicker.prototype.show = function (e) {

Just wrap setTimeout( 200 ms) inside this whole function, works well.

DavinderPRO commented 5 years ago

Hi, Not sure, if this is a bulletproof solution but this works for me especially with clockpicker. The version should be materializecss (v0.100.2). In materialize.js file Line No: 8913

ClockPicker.prototype.show = function (e) {

Just wrap setTimeout( 100 ms) inside this whole function, works well.

Now it works fine `

ClockPicker.prototype.show = function (e) { var _this = this;

setTimeout(function () { // Not show again if (_this.isShown) { return; }

raiseCallback(_this.options.beforeShow);
$(':input').each(function () {
  $(this).attr('tabindex', -1);
});
var self = _this; // Initialize

_this.input.blur();

_this.popover.addClass('picker--opened');

_this.input.addClass('picker__input picker__input--active');

$(document.body).css('overflow', 'hidden'); // Get the time

var value = ((_this.input.prop('value') || _this.options['default'] || '') + '').split(':');

if (_this.options.twelvehour && !(typeof value[1] === 'undefined')) {
  if (value[1].indexOf("AM") > 0) {
    _this.amOrPm = 'AM';
  } else {
    _this.amOrPm = 'PM';
  }

  value[1] = value[1].replace("AM", "").replace("PM", "");
}

if (value[0] === 'now') {
  var now = new Date(+new Date() + _this.options.fromnow);
  value = [now.getHours(), now.getMinutes()];

  if (_this.options.twelvehour) {
    _this.amOrPm = value[0] >= 12 && value[0] < 24 ? 'PM' : 'AM';
  }
}

_this.hours = +value[0] || 0;
_this.minutes = +value[1] || 0;

_this.spanHours.html(_this.hours);

_this.spanMinutes.html(leadingZero(_this.minutes));

if (!_this.isAppended) {
  // Append popover to input by default
  var containerEl = document.querySelector(_this.options.container);

  if (_this.options.container && containerEl) {
    containerEl.appendChild(_this.popover[0]);
  } else {
    _this.popover.insertAfter(_this.input);
  }

  if (_this.options.twelvehour) {
    if (_this.amOrPm === 'PM') {
      _this.spanAmPm.children('#click-pm').addClass("text-primary");

      _this.spanAmPm.children('#click-am').removeClass("text-primary");
    } else {
      _this.spanAmPm.children('#click-am').addClass("text-primary");

      _this.spanAmPm.children('#click-pm').removeClass("text-primary");
    }
  } // Reset position when resize

  $win.on('resize.clockpicker' + _this.id, function () {
    if (self.isShown) {
      self.locate();
    }
  });
  _this.isAppended = true;
} // Toggle to hours view

_this.toggleView('hours'); // Set position

_this.locate();

_this.isShown = true; // Hide when clicking or tabbing on any element except the clock and input

$doc.on('click.clockpicker.' + _this.id + ' focusin.clockpicker.' + _this.id, function (e) {
  var target = $(e.target);

  if (target.closest(self.popover.find('.picker__wrap')).length === 0 && target.closest(self.input).length === 0) {
    self.hide();
  }
}); // Hide when ESC is pressed

$doc.on('keyup.clockpicker.' + _this.id, function (e) {
  if (e.keyCode === 27) {
    self.hide();
  }
});
raiseCallback(_this.options.afterShow);

}, 200); };

`

subashdbc commented 5 years ago

@DavinderPRO This is what I mean, does this work for you?

// Show popover
  ClockPicker.prototype.show = function (e) {
    setTimeout(() => {
      // Not show again
      if (this.isShown) {
        return;
      }
      raiseCallback(this.options.beforeShow);
      $(':input').each(function () {
        $(this).attr('tabindex', -1);
      });

      var self = this;
      // Initialize
      this.input.blur();
      this.popover.addClass('picker--opened');
      this.input.addClass('picker__input picker__input--active');
      $(document.body).css('overflow', 'hidden');
      // Get the time
      var value = ((this.input.prop('value') || this.options['default'] || '') + '').split(':');
      if (this.options.twelvehour && !(typeof value[1] === 'undefined')) {
        if (value[1].indexOf("AM") > 0) {
          this.amOrPm = 'AM';
        } else {
          this.amOrPm = 'PM';
        }
        value[1] = value[1].replace("AM", "").replace("PM", "");
      }
      if (value[0] === 'now') {
        var now = new Date(+new Date() + this.options.fromnow);
        value = [now.getHours(), now.getMinutes()];
        if (this.options.twelvehour) {
          this.amOrPm = value[0] >= 12 && value[0] < 24 ? 'PM' : 'AM';
        }
      }
      this.hours = +value[0] || 0;
      this.minutes = +value[1] || 0;
      this.spanHours.html(this.hours);
      this.spanMinutes.html(leadingZero(this.minutes));
      if (!this.isAppended) {

        // Append popover to input by default
        var containerEl = document.querySelector(this.options.container);
        if (this.options.container && containerEl) {
          containerEl.appendChild(this.popover[0]);
        } else {
          this.popover.insertAfter(this.input);
        }

        if (this.options.twelvehour) {
          if (this.amOrPm === 'PM') {
            this.spanAmPm.children('#click-pm').addClass("text-primary");
            this.spanAmPm.children('#click-am').removeClass("text-primary");
          } else {
            this.spanAmPm.children('#click-am').addClass("text-primary");
            this.spanAmPm.children('#click-pm').removeClass("text-primary");
          }
        }
        // Reset position when resize
        $win.on('resize.clockpicker' + this.id, function () {
          if (self.isShown) {
            self.locate();
          }
        });
        this.isAppended = true;
      }
      // Toggle to hours view
      this.toggleView('hours');
      // Set position
      this.locate();
      this.isShown = true;
      // Hide when clicking or tabbing on any element except the clock and input
      $doc.on('click.clockpicker.' + this.id + ' focusin.clockpicker.' + this.id, function (e) {
        var target = $(e.target);
        if (target.closest(self.popover.find('.picker__wrap')).length === 0 && target.closest(self.input).length === 0) {
          self.hide();
        }
      });
      // Hide when ESC is pressed
      $doc.on('keyup.clockpicker.' + this.id, function (e) {
        if (e.keyCode === 27) {
          self.hide();
        }
      });
      raiseCallback(this.options.afterShow);
    }, 200);
  };
markonose commented 5 years ago

To fix the date picker change the code from:

// Only bind keydown events if the element isn’t editable.
if (!SETTINGS.editable) {

    $ELEMENT.

        // On focus/click, focus onto the root to open it up.
        on('focus.' + STATE.id + ' click.' + STATE.id, function (event) {
            event.preventDefault();
            P.$root.eq(0).focus();
        }).

        // Handle keyboard event based on the picker being opened or not.
        on('keydown.' + STATE.id, handleKeydownEvent);
}

to:

// Only bind keydown events if the element isn’t editable.
if (!SETTINGS.editable) {

    $ELEMENT.

        // On focus/click, focus onto the root to open it up.
        on('focus.' + STATE.id + ' click.' + STATE.id, function (event) {
            setTimeout(function(){
                event.preventDefault();
                P.$root.eq(0).focus();
            }, 100);
        }).

        // Handle keyboard event based on the picker being opened or not.
        on('keydown.' + STATE.id, handleKeydownEvent);
}
ivanhuay commented 5 years ago

+1

free6k commented 5 years ago

+1

ray007 commented 5 years ago

@markonose Are you sure that event.preventDefault(); works as intended in a setTimout()? I suspect you only want the .focus() call delayed.

markonose commented 5 years ago

@ray007 You are indeed correct, but at least in my case the event.preventDefault(); does nothing.

// On focus/click, focus onto the root to open it up.
on('focus.' + STATE.id + ' click.' + STATE.id, function (event) {
    event.preventDefault();

    setTimeout(function(){
        P.$root.eq(0).focus();
    }, 100);
}).

would work more like the original code intended

markonose commented 5 years ago

If anyone stumbles uppon this thread here's how to fix the problem for the select dropdown

$newSelect.on({
    'focus': function () {
        var _this = this;

        setTimeout(function () {
            if ($('ul.select-dropdown').not(options[0]).is(':visible')) {
                $('input.select-dropdown').trigger('close');
                $(window).off('click.select');
            }
            if (!options.is(':visible')) {
                $(_this).trigger('open', ['focus']);
                var label = $(_this).val();
                if (multiple && label.indexOf(',') >= 0) {
                    label = label.split(',')[0];
                }

                var selectedOption = options.find('li').filter(function () {
                    return $(_this).text().toLowerCase() === label.toLowerCase();
                })[0];
                activateOption(options, selectedOption, true);

                $(window).off('click.select').on('click.select', function () {
                    multiple && (optionsHover || $newSelect.trigger('close'));
                    $(window).off('click.select');
                });
            }
        }, 75);
    },
    'click': function (e) {
        e.stopPropagation();
    }
});
Dogfalo commented 5 years ago

1.0.0 does not have this problem so if you are able to upgrade, I would recommend that route. However if you can't, try one of the fixes in this thread. Potentially we may add a fix for this if chrome doesn't revert this behavior.

Sebastriani commented 5 years ago

@Dogfalo do you know what was the change introduced in Chrome that produces this behaviour?

Dogfalo commented 5 years ago

I think it has to do with the timing of events when clicking an input. Where before it was click -> focus, it seems to now be focus -> click. This is just a guess as I haven't looked deeply into the issue yet.

DanielRuf commented 5 years ago

See https://bugs.chromium.org/p/chromium/issues/detail?id=941910#c6

pratappp commented 5 years ago

If anyone stumbles uppon this thread here's how to fix the problem for the select dropdown

$newSelect.on({
  'focus': function () {
      var _this = this;

      setTimeout(function () {
          if ($('ul.select-dropdown').not(options[0]).is(':visible')) {
              $('input.select-dropdown').trigger('close');
              $(window).off('click.select');
          }
          if (!options.is(':visible')) {
              $(_this).trigger('open', ['focus']);
              var label = $(_this).val();
              if (multiple && label.indexOf(',') >= 0) {
                  label = label.split(',')[0];
              }

              var selectedOption = options.find('li').filter(function () {
                  return $(_this).text().toLowerCase() === label.toLowerCase();
              })[0];
              activateOption(options, selectedOption, true);

              $(window).off('click.select').on('click.select', function () {
                  multiple && (optionsHover || $newSelect.trigger('close'));
                  $(window).off('click.select');
              });
          }
      }, 75);
  },
  'click': function (e) {
      e.stopPropagation();
  }
});

Above fix was not showing the already selected item from drop-down hence i modified it little bit. below is changed code.

$newSelect.on({ 'focus': function () {

                    if ($('ul.select-dropdown').not(options[0]).is(':visible')) {
                        $('input.select-dropdown').trigger('close');
                    }
                    if (!options.is(':visible')) {       

                        setTimeout(function () {
                            $(this).trigger('open', ['focus']);
                        }, 70);

                        var label = $(this).val();
                        var selectedOption = options.find('li').filter(function () {
                            return $(this).text().toLowerCase() === label.toLowerCase();
                        })[0];
                        activateOption(options, selectedOption);
                    }

            },
            'click': function (e) {
                e.stopPropagation();
            },
Sebastriani commented 5 years ago

@Dogfalo Version 74 of Chrome is released and they didn't fix this, reading the comment 23 in https://bugs.chromium.org/p/chromium/issues/detail?id=941910#c6 it doesn't seem they are going to fix it, couldn't you please just fix it in materializecss?

DanielRuf commented 5 years ago

@Dogfalo Version 74 of Chrome is released and they didn't fix this, reading the comment 23 in bugs.chromium.org/p/chromium/issues/detail?id=941910#c6 it doesn't seem they are going to fix it, couldn't you please just fix it in materializecss?

Well it will not fix old releases as 0.x is not actively developed anymore but 1.x. Also this is opensource so anyone can provide a PR to fix this. We had to do another workaround as the timeout / debouncing did not completely fix this bug in Chrome.

armando-navarro-frequence commented 5 years ago

I thought this may help some who stumbled on this page. I am using angular2-materialize and my materialize select input sometimes closes immediately after opening.

I solved this by adding a click listener on a div that wraps the select tag, with nothing but event.stopPropagation() in the listener. This fixed the issue for me.

GalaxyShadesCat commented 5 years ago

materialize.js

For anyone who is too lazy to edit the file yourself

DanielRuf commented 5 years ago

@LemuelHui

Awesome =)

Is there also a minified patched version? If you need help with that, just let me know.

DanielRuf commented 5 years ago

PS: the timeout does not really fix that (longpress click).

You can check how we have fixed it in pickadate.js.

Sebastriani commented 5 years ago

@DanielRuf could you provide a link to the commit with the fix that you did for pickadate?

DanielRuf commented 5 years ago

@Sebastriani see https://github.com/amsul/pickadate.js/commit/31789ebf7fb9c832f0e8e06003654e20ab91c63d

My initial approach using timers (with setTimeout) did not work in all but many cases as it was added at the top of the counter stack of the VM.

NavidZ commented 5 years ago

The quick fix in the original codepen example (https://codepen.io/anon/pen/evQxPy) in this issue would be a code snippet like this: document.getElementById('birthdate').addEventListener('pointerdown', (e)=>{e.target.setPointerCapture(e.pointerId);}) I was going to send a pull request to fix this issue, but I'm having some trouble with this repo and understanding how I should get a local example with un-built files to work. I can see from the documentations that we should not modify materialize.js directly. But I'm not able to produce a local example similar to the codepen example that includes the unbuilt files to verify the fix locally before sending the pull request. Can someone guide me here on what files from js/ directory I should include + the html/css in the code pen to reproduce the issue locally? Alternatively if you could just attach a html file that include the local files from this repo and reproduce the issue and I can work on the fix.