@everymatrix/helper-filters 0.1.1 → 0.1.5

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.
@@ -41,7 +41,7 @@ const translate$1 = (key, customLang) => {
41
41
  */
42
42
  class Lumo extends HTMLElement {
43
43
  static get version() {
44
- return '23.1.5';
44
+ return '23.2.0';
45
45
  }
46
46
  }
47
47
 
@@ -97,7 +97,7 @@ const ThemePropertyMixin = (superClass) =>
97
97
  * **NOTE:** Extending the mixin only provides the property for binding,
98
98
  * and does not make the propagation alone.
99
99
  *
100
- * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/ds/customization/styling-components/#sub-components).
100
+ * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components/#sub-components).
101
101
  * page for more information.
102
102
  *
103
103
  * @deprecated The `theme` property is not supposed for public use and will be dropped in Vaadin 24.
@@ -122,7 +122,7 @@ const ThemePropertyMixin = (superClass) =>
122
122
  * **NOTE:** Extending the mixin only provides the property for binding,
123
123
  * and does not make the propagation alone.
124
124
  *
125
- * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/ds/customization/styling-components/#sub-components).
125
+ * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components/#sub-components).
126
126
  * page for more information.
127
127
  *
128
128
  * @protected
@@ -227,9 +227,9 @@ function matchesThemeFor(themeFor, tagName) {
227
227
  */
228
228
  function getIncludePriority(moduleName = '') {
229
229
  let includePriority = 0;
230
- if (moduleName.indexOf('lumo-') === 0 || moduleName.indexOf('material-') === 0) {
230
+ if (moduleName.startsWith('lumo-') || moduleName.startsWith('material-')) {
231
231
  includePriority = 1;
232
- } else if (moduleName.indexOf('vaadin-') === 0) {
232
+ } else if (moduleName.startsWith('vaadin-')) {
233
233
  includePriority = 2;
234
234
  }
235
235
  return includePriority;
@@ -10114,6 +10114,38 @@ const ControllerMixin = dedupingMixin(
10114
10114
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
10115
10115
  */
10116
10116
 
10117
+ // We consider the keyboard to be active if the window has received a keydown
10118
+ // event since the last mousedown event.
10119
+ let keyboardActive = false;
10120
+
10121
+ // Listen for top-level keydown and mousedown events.
10122
+ // Use capture phase so we detect events even if they're handled.
10123
+ window.addEventListener(
10124
+ 'keydown',
10125
+ () => {
10126
+ keyboardActive = true;
10127
+ },
10128
+ { capture: true },
10129
+ );
10130
+
10131
+ window.addEventListener(
10132
+ 'mousedown',
10133
+ () => {
10134
+ keyboardActive = false;
10135
+ },
10136
+ { capture: true },
10137
+ );
10138
+
10139
+ /**
10140
+ * Returns true if the window has received a keydown
10141
+ * event since the last mousedown event.
10142
+ *
10143
+ * @return {boolean}
10144
+ */
10145
+ function isKeyboardActive() {
10146
+ return keyboardActive;
10147
+ }
10148
+
10117
10149
  /**
10118
10150
  * Returns true if the element is hidden directly with `display: none` or `visibility: hidden`,
10119
10151
  * false otherwise.
@@ -10554,7 +10586,7 @@ class FocusTrapController {
10554
10586
  * ---|---|---
10555
10587
  * `--vaadin-overlay-viewport-bottom` | Bottom offset of the visible viewport area | `0` or detected offset
10556
10588
  *
10557
- * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
10589
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
10558
10590
  *
10559
10591
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
10560
10592
  * @fires {CustomEvent} vaadin-overlay-open - Fired after the overlay is opened.
@@ -12824,7 +12856,7 @@ const registered = new Set();
12824
12856
  const ElementMixin = (superClass) =>
12825
12857
  class VaadinElementMixin extends DirMixin(superClass) {
12826
12858
  static get version() {
12827
- return '23.1.5';
12859
+ return '23.2.0';
12828
12860
  }
12829
12861
 
12830
12862
  /** @protected */
@@ -13122,7 +13154,7 @@ function _handleNative(ev) {
13122
13154
  }
13123
13155
  if (!ev[HANDLED_OBJ]) {
13124
13156
  ev[HANDLED_OBJ] = {};
13125
- if (type.slice(0, 5) === 'touch') {
13157
+ if (type.startsWith('touch')) {
13126
13158
  const t = ev.changedTouches[0];
13127
13159
  if (type === 'touchstart') {
13128
13160
  // Only handle the first finger
@@ -13981,28 +14013,6 @@ const ActiveMixin = (superclass) =>
13981
14013
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
13982
14014
  */
13983
14015
 
13984
- // We consider the keyboard to be active if the window has received a keydown
13985
- // event since the last mousedown event.
13986
- let keyboardActive = false;
13987
-
13988
- // Listen for top-level keydown and mousedown events.
13989
- // Use capture phase so we detect events even if they're handled.
13990
- window.addEventListener(
13991
- 'keydown',
13992
- () => {
13993
- keyboardActive = true;
13994
- },
13995
- { capture: true },
13996
- );
13997
-
13998
- window.addEventListener(
13999
- 'mousedown',
14000
- () => {
14001
- keyboardActive = false;
14002
- },
14003
- { capture: true },
14004
- );
14005
-
14006
14016
  /**
14007
14017
  * A mixin to handle `focused` and `focus-ring` attributes based on focus.
14008
14018
  *
@@ -14016,7 +14026,7 @@ const FocusMixin = dedupingMixin(
14016
14026
  * @return {boolean}
14017
14027
  */
14018
14028
  get _keyboardActive() {
14019
- return keyboardActive;
14029
+ return isKeyboardActive();
14020
14030
  }
14021
14031
 
14022
14032
  /** @protected */
@@ -14280,7 +14290,7 @@ const ButtonMixin = (superClass) =>
14280
14290
  * `focus-ring` | Set when the button is focused using the keyboard.
14281
14291
  * `focused` | Set when the button is focused.
14282
14292
  *
14283
- * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
14293
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
14284
14294
  *
14285
14295
  * @extends HTMLElement
14286
14296
  * @mixes ButtonMixin
@@ -14362,7 +14372,6 @@ registerStyles(
14362
14372
  i$1`
14363
14373
  :host {
14364
14374
  position: relative;
14365
- background-color: transparent;
14366
14375
  /* Background for the year scroller, placed here as we are using a mask image on the actual years part */
14367
14376
  background-image: linear-gradient(var(--lumo-shade-5pct), var(--lumo-shade-5pct));
14368
14377
  background-size: 57px 100%;
@@ -14470,17 +14479,10 @@ registerStyles(
14470
14479
 
14471
14480
  [part='toolbar'] {
14472
14481
  padding: var(--lumo-space-s);
14473
- box-shadow: 0 -1px 0 0 var(--lumo-contrast-10pct);
14474
14482
  border-bottom-left-radius: var(--lumo-border-radius-l);
14475
14483
  margin-right: 57px;
14476
14484
  }
14477
14485
 
14478
- @supports (mask-image: linear-gradient(#000, #000)) or (-webkit-mask-image: linear-gradient(#000, #000)) {
14479
- [part='toolbar'] {
14480
- box-shadow: none;
14481
- }
14482
- }
14483
-
14484
14486
  /* Today and Cancel buttons */
14485
14487
 
14486
14488
  [part='toolbar'] [part\$='button'] {
@@ -14513,8 +14515,6 @@ registerStyles(
14513
14515
  /* Very narrow screen (year scroller initially hidden) */
14514
14516
 
14515
14517
  [part='years-toggle-button'] {
14516
- position: relative;
14517
- right: auto;
14518
14518
  display: flex;
14519
14519
  align-items: center;
14520
14520
  height: var(--lumo-size-s);
@@ -14532,10 +14532,6 @@ registerStyles(
14532
14532
  color: var(--lumo-primary-contrast-color);
14533
14533
  }
14534
14534
 
14535
- [part='years-toggle-button']::before {
14536
- content: none;
14537
- }
14538
-
14539
14535
  /* TODO magic number (same as used for iron-media-query in vaadin-date-picker-overlay-content) */
14540
14536
  @media screen and (max-width: 374px) {
14541
14537
  :host {
@@ -14711,9 +14707,9 @@ registerStyles(
14711
14707
  { moduleId: 'lumo-month-calendar' },
14712
14708
  );
14713
14709
 
14714
- const $_documentContainer$1 = document.createElement('template');
14710
+ const template$1 = document.createElement('template');
14715
14711
 
14716
- $_documentContainer$1.innerHTML = `
14712
+ template$1.innerHTML = `
14717
14713
  <style>
14718
14714
  @keyframes vaadin-date-picker-month-calendar-focus-date {
14719
14715
  50% {
@@ -14723,7 +14719,7 @@ $_documentContainer$1.innerHTML = `
14723
14719
  </style>
14724
14720
  `;
14725
14721
 
14726
- document.head.appendChild($_documentContainer$1.content);
14722
+ document.head.appendChild(template$1.content);
14727
14723
 
14728
14724
  /**
14729
14725
  * @license
@@ -14731,9 +14727,9 @@ document.head.appendChild($_documentContainer$1.content);
14731
14727
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
14732
14728
  */
14733
14729
 
14734
- const $_documentContainer = document.createElement('template');
14730
+ const template = document.createElement('template');
14735
14731
 
14736
- $_documentContainer.innerHTML = `
14732
+ template.innerHTML = `
14737
14733
  <style>
14738
14734
  @font-face {
14739
14735
  font-family: 'lumo-icons';
@@ -14789,7 +14785,7 @@ $_documentContainer.innerHTML = `
14789
14785
  </style>
14790
14786
  `;
14791
14787
 
14792
- document.head.appendChild($_documentContainer.content);
14788
+ document.head.appendChild(template.content);
14793
14789
 
14794
14790
  /**
14795
14791
  * @license
@@ -15356,6 +15352,57 @@ function getAncestorRootNodes(node) {
15356
15352
  return result;
15357
15353
  }
15358
15354
 
15355
+ /**
15356
+ * @param {string} value
15357
+ * @return {Set<string>}
15358
+ */
15359
+ function deserializeAttributeValue(value) {
15360
+ if (!value) {
15361
+ return new Set();
15362
+ }
15363
+
15364
+ return new Set(value.split(' '));
15365
+ }
15366
+
15367
+ /**
15368
+ * @param {Set<string>} values
15369
+ * @return {string}
15370
+ */
15371
+ function serializeAttributeValue(values) {
15372
+ return [...values].join(' ');
15373
+ }
15374
+
15375
+ /**
15376
+ * Adds a value to an attribute containing space-delimited values.
15377
+ *
15378
+ * @param {HTMLElement} element
15379
+ * @param {string} attr
15380
+ * @param {string} value
15381
+ */
15382
+ function addValueToAttribute(element, attr, value) {
15383
+ const values = deserializeAttributeValue(element.getAttribute(attr));
15384
+ values.add(value);
15385
+ element.setAttribute(attr, serializeAttributeValue(values));
15386
+ }
15387
+
15388
+ /**
15389
+ * Removes a value from an attribute containing space-delimited values.
15390
+ * If the value is the last one, the whole attribute is removed.
15391
+ *
15392
+ * @param {HTMLElement} element
15393
+ * @param {string} attr
15394
+ * @param {string} value
15395
+ */
15396
+ function removeValueFromAttribute(element, attr, value) {
15397
+ const values = deserializeAttributeValue(element.getAttribute(attr));
15398
+ values.delete(value);
15399
+ if (values.size === 0) {
15400
+ element.removeAttribute(attr);
15401
+ return;
15402
+ }
15403
+ element.setAttribute(attr, serializeAttributeValue(values));
15404
+ }
15405
+
15359
15406
  /**
15360
15407
  * @license
15361
15408
  * Copyright (c) 2017 - 2022 Vaadin Ltd.
@@ -17080,7 +17127,9 @@ class MonthCalendar extends FocusMixin(ThemableMixin(PolymerElement)) {
17080
17127
 
17081
17128
  _onMonthGridTouchStart() {
17082
17129
  this._notTapping = false;
17083
- setTimeout(() => (this._notTapping = true), 300);
17130
+ setTimeout(() => {
17131
+ this._notTapping = true;
17132
+ }, 300);
17084
17133
  }
17085
17134
 
17086
17135
  _dateAdd(date, delta) {
@@ -17415,7 +17464,7 @@ class InfiniteScroller extends PolymerElement {
17415
17464
  // Once the first set of items start fading in, stamp the rest
17416
17465
  this._buffers.forEach((buffer) => {
17417
17466
  [].forEach.call(buffer.children, (insertionPoint) => this._ensureStampedInstance(insertionPoint._itemWrapper));
17418
- }, this);
17467
+ });
17419
17468
 
17420
17469
  if (!this._buffers[0].translateY) {
17421
17470
  this._reset();
@@ -17578,7 +17627,7 @@ class InfiniteScroller extends PolymerElement {
17578
17627
  }
17579
17628
  }, 1); // Wait for first reset
17580
17629
  }
17581
- }, this);
17630
+ });
17582
17631
 
17583
17632
  setTimeout(() => {
17584
17633
  afterNextRender(this, this._finishInit.bind(this));
@@ -17616,7 +17665,7 @@ class InfiniteScroller extends PolymerElement {
17616
17665
  });
17617
17666
  buffer.updated = true;
17618
17667
  }
17619
- }, this);
17668
+ });
17620
17669
  }
17621
17670
 
17622
17671
  _isVisible(element, container) {
@@ -17715,7 +17764,6 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
17715
17764
  height: 100%;
17716
17765
  width: 100%;
17717
17766
  outline: none;
17718
- background: #fff;
17719
17767
  }
17720
17768
 
17721
17769
  [part='overlay-header'] {
@@ -17733,22 +17781,14 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
17733
17781
  flex-grow: 1;
17734
17782
  }
17735
17783
 
17736
- [part='clear-button']:not([showclear]) {
17737
- display: none;
17784
+ [hidden] {
17785
+ display: none !important;
17738
17786
  }
17739
17787
 
17740
17788
  [part='years-toggle-button'] {
17741
17789
  display: flex;
17742
17790
  }
17743
17791
 
17744
- [part='years-toggle-button'][desktop] {
17745
- display: none;
17746
- }
17747
-
17748
- :host(:not([years-visible])) [part='years-toggle-button']::before {
17749
- transform: rotate(180deg);
17750
- }
17751
-
17752
17792
  #scrollers {
17753
17793
  display: flex;
17754
17794
  height: 100%;
@@ -17822,27 +17862,14 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
17822
17862
  z-index: 2;
17823
17863
  flex-shrink: 0;
17824
17864
  }
17825
-
17826
- [part~='overlay-header']:not([desktop]) {
17827
- padding-bottom: 40px;
17828
- }
17829
-
17830
- [part~='years-toggle-button'] {
17831
- position: absolute;
17832
- top: auto;
17833
- right: 8px;
17834
- bottom: 0;
17835
- z-index: 1;
17836
- padding: 8px;
17837
- }
17838
17865
  </style>
17839
17866
 
17840
17867
  <div part="overlay-header" on-touchend="_preventDefault" desktop$="[[_desktopMode]]" aria-hidden="true">
17841
17868
  <div part="label">[[_formatDisplayed(selectedDate, i18n.formatDate, label)]]</div>
17842
- <div part="clear-button" showclear$="[[_showClear(selectedDate)]]"></div>
17869
+ <div part="clear-button" hidden$="[[!selectedDate]]"></div>
17843
17870
  <div part="toggle-button"></div>
17844
17871
 
17845
- <div part="years-toggle-button" desktop$="[[_desktopMode]]" aria-hidden="true">
17872
+ <div part="years-toggle-button" hidden$="[[_desktopMode]]" aria-hidden="true">
17846
17873
  [[_yearAfterXMonths(_visibleMonthIndex)]]
17847
17874
  </div>
17848
17875
  </div>
@@ -17928,6 +17955,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
17928
17955
  */
17929
17956
  selectedDate: {
17930
17957
  type: Date,
17958
+ value: null,
17931
17959
  },
17932
17960
 
17933
17961
  /**
@@ -18003,10 +18031,12 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18003
18031
  return this.getAttribute('dir') === 'rtl';
18004
18032
  }
18005
18033
 
18034
+ get calendars() {
18035
+ return [...this.shadowRoot.querySelectorAll('vaadin-month-calendar')];
18036
+ }
18037
+
18006
18038
  get focusableDateElement() {
18007
- return [...this.shadowRoot.querySelectorAll('vaadin-month-calendar')]
18008
- .map((calendar) => calendar.focusableDateElement)
18009
- .find(Boolean);
18039
+ return this.calendars.map((calendar) => calendar.focusableDateElement).find(Boolean);
18010
18040
  }
18011
18041
 
18012
18042
  ready() {
@@ -18014,7 +18044,6 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18014
18044
 
18015
18045
  this.setAttribute('role', 'dialog');
18016
18046
 
18017
- addListener(this, 'tap', this._stopPropagation);
18018
18047
  addListener(this.$.scrollers, 'track', this._track.bind(this));
18019
18048
  addListener(this.shadowRoot.querySelector('[part="clear-button"]'), 'tap', this._clear.bind(this));
18020
18049
  addListener(this.shadowRoot.querySelector('[part="today-button"]'), 'tap', this._onTodayTap.bind(this));
@@ -18136,7 +18165,9 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18136
18165
 
18137
18166
  _onYearScrollTouchStart() {
18138
18167
  this._notTapping = false;
18139
- setTimeout(() => (this._notTapping = true), 300);
18168
+ setTimeout(() => {
18169
+ this._notTapping = true;
18170
+ }, 300);
18140
18171
 
18141
18172
  this._repositionMonthScroller();
18142
18173
  }
@@ -18147,7 +18178,9 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18147
18178
 
18148
18179
  _doIgnoreTaps() {
18149
18180
  this._ignoreTaps = true;
18150
- this._debouncer = Debouncer$1.debounce(this._debouncer, timeOut.after(300), () => (this._ignoreTaps = false));
18181
+ this._debouncer = Debouncer$1.debounce(this._debouncer, timeOut.after(300), () => {
18182
+ this._ignoreTaps = false;
18183
+ });
18151
18184
  }
18152
18185
 
18153
18186
  _formatDisplayed(date, formatDate, label) {
@@ -18178,10 +18211,6 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18178
18211
  this.scrollToDate(new Date(), true);
18179
18212
  }
18180
18213
 
18181
- _showClear(selectedDate) {
18182
- return !!selectedDate;
18183
- }
18184
-
18185
18214
  _onYearTap(e) {
18186
18215
  if (!this._ignoreTaps && !this._notTapping) {
18187
18216
  const scrollDelta =
@@ -18207,6 +18236,11 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18207
18236
 
18208
18237
  this._targetPosition = targetPosition;
18209
18238
 
18239
+ let revealResolve;
18240
+ this._revealPromise = new Promise((resolve) => {
18241
+ revealResolve = resolve;
18242
+ });
18243
+
18210
18244
  // http://gizma.com/easing/
18211
18245
  const easingFunction = (t, b, c, d) => {
18212
18246
  t /= d / 2;
@@ -18247,7 +18281,9 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18247
18281
 
18248
18282
  this.$.monthScroller.position = this._targetPosition;
18249
18283
  this._targetPosition = undefined;
18250
- this.__tryFocusDate();
18284
+
18285
+ revealResolve();
18286
+ this._revealPromise = undefined;
18251
18287
  }
18252
18288
 
18253
18289
  setTimeout(this._repositionYearScroller.bind(this), 1);
@@ -18457,51 +18493,44 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18457
18493
  switch (section) {
18458
18494
  case 'calendar':
18459
18495
  if (event.shiftKey) {
18460
- // Return focus back to the input field.
18461
18496
  event.preventDefault();
18462
- this.__focusInput();
18497
+
18498
+ if (this.hasAttribute('fullscreen')) {
18499
+ // Trap focus in the overlay
18500
+ this.$.cancelButton.focus();
18501
+ } else {
18502
+ this.__focusInput();
18503
+ }
18463
18504
  }
18464
18505
  break;
18465
18506
  case 'today':
18466
18507
  if (event.shiftKey) {
18467
- // Browser returns focus back to the calendar.
18468
- // We need to move the scroll to focused date.
18469
- setTimeout(() => this.revealDate(this.focusedDate), 1);
18508
+ event.preventDefault();
18509
+ this.focusDateElement();
18470
18510
  }
18471
18511
  break;
18472
18512
  case 'cancel':
18473
18513
  if (!event.shiftKey) {
18474
- // Return focus back to the input field.
18475
18514
  event.preventDefault();
18476
- this.__focusInput();
18515
+
18516
+ if (this.hasAttribute('fullscreen')) {
18517
+ // Trap focus in the overlay
18518
+ this.focusDateElement();
18519
+ } else {
18520
+ this.__focusInput();
18521
+ }
18477
18522
  }
18478
18523
  break;
18479
18524
  }
18480
18525
  }
18481
18526
 
18482
18527
  __onTodayButtonKeyDown(event) {
18483
- if (this.hasAttribute('fullscreen')) {
18484
- // Do not prevent closing on Esc
18485
- if (event.key !== 'Escape') {
18486
- event.stopPropagation();
18487
- }
18488
- return;
18489
- }
18490
-
18491
18528
  if (event.key === 'Tab') {
18492
18529
  this._onTabKeyDown(event, 'today');
18493
18530
  }
18494
18531
  }
18495
18532
 
18496
18533
  __onCancelButtonKeyDown(event) {
18497
- if (this.hasAttribute('fullscreen')) {
18498
- // Do not prevent closing on Esc
18499
- if (event.key !== 'Escape') {
18500
- event.stopPropagation();
18501
- }
18502
- return;
18503
- }
18504
-
18505
18534
  if (event.key === 'Tab') {
18506
18535
  this._onTabKeyDown(event, 'cancel');
18507
18536
  }
@@ -18530,15 +18559,29 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18530
18559
  if (!keepMonth) {
18531
18560
  this._focusedMonthDate = dateToFocus.getDate();
18532
18561
  }
18533
- await this.focusDateElement();
18562
+ await this.focusDateElement(false);
18534
18563
  }
18535
18564
 
18536
- async focusDateElement() {
18565
+ async focusDateElement(reveal = true) {
18537
18566
  this.__pendingDateFocus = this.focusedDate;
18538
18567
 
18539
- await new Promise((resolve) => {
18540
- requestAnimationFrame(resolve);
18541
- });
18568
+ // Wait for `vaadin-month-calendar` elements to be rendered
18569
+ if (!this.calendars.length) {
18570
+ await new Promise((resolve) => {
18571
+ setTimeout(resolve);
18572
+ });
18573
+ }
18574
+
18575
+ // Reveal focused date unless it has been just set,
18576
+ // which triggers `revealDate()` in the observer.
18577
+ if (reveal) {
18578
+ this.revealDate(this.focusedDate);
18579
+ }
18580
+
18581
+ if (this._revealPromise) {
18582
+ // Wait for focused date to be scrolled into view.
18583
+ await this._revealPromise;
18584
+ }
18542
18585
 
18543
18586
  this.__tryFocusDate();
18544
18587
  }
@@ -18636,10 +18679,6 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18636
18679
  todayMidnight.setDate(today.getDate());
18637
18680
  return this._dateAllowed(todayMidnight, min, max);
18638
18681
  }
18639
-
18640
- _stopPropagation(e) {
18641
- e.stopPropagation();
18642
- }
18643
18682
  }
18644
18683
 
18645
18684
  customElements.define(DatePickerOverlayContent.is, DatePickerOverlayContent);
@@ -18870,6 +18909,24 @@ const DelegateFocusMixin = dedupingMixin(
18870
18909
  },
18871
18910
  );
18872
18911
 
18912
+ /**
18913
+ * @license
18914
+ * Copyright (c) 2021 - 2022 Vaadin Ltd.
18915
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
18916
+ */
18917
+
18918
+ let uniqueId = 0;
18919
+
18920
+ /**
18921
+ * Returns a unique integer id.
18922
+ *
18923
+ * @return {number}
18924
+ */
18925
+ function generateUniqueId() {
18926
+ // eslint-disable-next-line no-plusplus
18927
+ return uniqueId++;
18928
+ }
18929
+
18873
18930
  /**
18874
18931
  * @license
18875
18932
  * Copyright (c) 2021 - 2022 Vaadin Ltd.
@@ -18890,24 +18947,21 @@ class SlotController extends EventTarget {
18890
18947
  */
18891
18948
  static generateId(slotName, host) {
18892
18949
  const prefix = slotName || 'default';
18893
-
18894
- // Support dash-case slot names e.g. "error-message"
18895
- const field = `${dashToCamelCase(prefix)}Id`;
18896
-
18897
- // Maintain the unique ID counter for a given prefix.
18898
- this[field] = 1 + this[field] || 0;
18899
-
18900
- return `${prefix}-${host.localName}-${this[field]}`;
18950
+ return `${prefix}-${host.localName}-${generateUniqueId()}`;
18901
18951
  }
18902
18952
 
18903
- constructor(host, slotName, slotFactory, slotInitializer) {
18953
+ constructor(host, slotName, slotFactory, slotInitializer, useUniqueId) {
18904
18954
  super();
18905
18955
 
18906
18956
  this.host = host;
18907
18957
  this.slotName = slotName;
18908
18958
  this.slotFactory = slotFactory;
18909
18959
  this.slotInitializer = slotInitializer;
18910
- this.defaultId = SlotController.generateId(slotName, host);
18960
+
18961
+ // Only generate the default ID if requested by the controller.
18962
+ if (useUniqueId) {
18963
+ this.defaultId = SlotController.generateId(slotName, host);
18964
+ }
18911
18965
  }
18912
18966
 
18913
18967
  hostConnected() {
@@ -19062,6 +19116,7 @@ class ErrorController extends SlotController {
19062
19116
 
19063
19117
  this.__updateHasError();
19064
19118
  },
19119
+ true,
19065
19120
  );
19066
19121
  }
19067
19122
 
@@ -19176,63 +19231,6 @@ class ErrorController extends SlotController {
19176
19231
  }
19177
19232
  }
19178
19233
 
19179
- /**
19180
- * @license
19181
- * Copyright (c) 2021 - 2022 Vaadin Ltd.
19182
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
19183
- */
19184
-
19185
- /**
19186
- * @param {string} value
19187
- * @return {Set<string>}
19188
- */
19189
- function deserializeAttributeValue(value) {
19190
- if (!value) {
19191
- return new Set();
19192
- }
19193
-
19194
- return new Set(value.split(' '));
19195
- }
19196
-
19197
- /**
19198
- * @param {Set<string>} values
19199
- * @return {string}
19200
- */
19201
- function serializeAttributeValue(values) {
19202
- return [...values].join(' ');
19203
- }
19204
-
19205
- /**
19206
- * Adds a value to an attribute containing space-delimited values.
19207
- *
19208
- * @param {HTMLElement} element
19209
- * @param {string} attr
19210
- * @param {string} value
19211
- */
19212
- function addValueToAttribute(element, attr, value) {
19213
- const values = deserializeAttributeValue(element.getAttribute(attr));
19214
- values.add(value);
19215
- element.setAttribute(attr, serializeAttributeValue(values));
19216
- }
19217
-
19218
- /**
19219
- * Removes a value from an attribute containing space-delimited values.
19220
- * If the value is the last one, the whole attribute is removed.
19221
- *
19222
- * @param {HTMLElement} element
19223
- * @param {string} attr
19224
- * @param {string} value
19225
- */
19226
- function removeValueFromAttribute(element, attr, value) {
19227
- const values = deserializeAttributeValue(element.getAttribute(attr));
19228
- values.delete(value);
19229
- if (values.size === 0) {
19230
- element.removeAttribute(attr);
19231
- return;
19232
- }
19233
- element.setAttribute(attr, serializeAttributeValue(values));
19234
- }
19235
-
19236
19234
  /**
19237
19235
  * @license
19238
19236
  * Copyright (c) 2021 - 2022 Vaadin Ltd.
@@ -19407,7 +19405,7 @@ class FieldAriaController {
19407
19405
 
19408
19406
  /**
19409
19407
  * @license
19410
- * Copyright (c) 2021 Vaadin Ltd.
19408
+ * Copyright (c) 2021 - 2022 Vaadin Ltd.
19411
19409
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
19412
19410
  */
19413
19411
 
@@ -19417,7 +19415,7 @@ class FieldAriaController {
19417
19415
  class HelperController extends SlotController {
19418
19416
  constructor(host) {
19419
19417
  // Do not provide slot factory, as only create helper lazily.
19420
- super(host, 'helper');
19418
+ super(host, 'helper', null, null, true);
19421
19419
  }
19422
19420
 
19423
19421
  get helperId() {
@@ -19614,6 +19612,7 @@ class LabelController extends SlotController {
19614
19612
 
19615
19613
  this.__observeLabel(node);
19616
19614
  },
19615
+ true,
19617
19616
  );
19618
19617
  }
19619
19618
 
@@ -19820,6 +19819,12 @@ const LabelMixin = dedupingMixin(
19820
19819
  super();
19821
19820
 
19822
19821
  this._labelController = new LabelController(this);
19822
+ }
19823
+
19824
+ /** @protected */
19825
+ ready() {
19826
+ super.ready();
19827
+
19823
19828
  this.addController(this._labelController);
19824
19829
  }
19825
19830
 
@@ -19867,12 +19872,17 @@ const ValidateMixin = dedupingMixin(
19867
19872
  }
19868
19873
 
19869
19874
  /**
19870
- * Returns true if field is valid, and sets `invalid` based on the field validity.
19875
+ * Validates the field and sets the `invalid` property based on the result.
19876
+ *
19877
+ * The method fires a `validated` event with the result of the validation.
19871
19878
  *
19872
19879
  * @return {boolean} True if the value is valid.
19873
19880
  */
19874
19881
  validate() {
19875
- return !(this.invalid = !this.checkValidity());
19882
+ const isValid = this.checkValidity();
19883
+ this._setInvalid(!isValid);
19884
+ this.dispatchEvent(new CustomEvent('validated', { detail: { valid: isValid } }));
19885
+ return isValid;
19876
19886
  }
19877
19887
 
19878
19888
  /**
@@ -19883,6 +19893,35 @@ const ValidateMixin = dedupingMixin(
19883
19893
  checkValidity() {
19884
19894
  return !this.required || !!this.value;
19885
19895
  }
19896
+
19897
+ /**
19898
+ * @param {boolean} invalid
19899
+ * @protected
19900
+ */
19901
+ _setInvalid(invalid) {
19902
+ if (this._shouldSetInvalid(invalid)) {
19903
+ this.invalid = invalid;
19904
+ }
19905
+ }
19906
+
19907
+ /**
19908
+ * Override this method to define whether the given `invalid` state should be set.
19909
+ *
19910
+ * @param {boolean} _invalid
19911
+ * @return {boolean}
19912
+ * @protected
19913
+ */
19914
+ _shouldSetInvalid(_invalid) {
19915
+ return true;
19916
+ }
19917
+
19918
+ /**
19919
+ * Fired whenever the field is validated.
19920
+ *
19921
+ * @event validated
19922
+ * @param {Object} detail
19923
+ * @param {boolean} detail.valid the result of the validation.
19924
+ */
19886
19925
  },
19887
19926
  );
19888
19927
 
@@ -19971,10 +20010,6 @@ const FieldMixin = (superclass) =>
19971
20010
  this._helperController = new HelperController(this);
19972
20011
  this._errorController = new ErrorController(this);
19973
20012
 
19974
- this.addController(this._fieldAriaController);
19975
- this.addController(this._helperController);
19976
- this.addController(this._errorController);
19977
-
19978
20013
  this._labelController.addEventListener('label-changed', (event) => {
19979
20014
  const { hasLabel, node } = event.detail;
19980
20015
  this.__labelChanged(hasLabel, node);
@@ -19986,6 +20021,15 @@ const FieldMixin = (superclass) =>
19986
20021
  });
19987
20022
  }
19988
20023
 
20024
+ /** @protected */
20025
+ ready() {
20026
+ super.ready();
20027
+
20028
+ this.addController(this._fieldAriaController);
20029
+ this.addController(this._helperController);
20030
+ this.addController(this._errorController);
20031
+ }
20032
+
19989
20033
  /** @private */
19990
20034
  __helperChanged(hasHelper, helperNode) {
19991
20035
  if (hasHelper) {
@@ -20041,7 +20085,7 @@ const FieldMixin = (superclass) =>
20041
20085
  }
20042
20086
 
20043
20087
  /**
20044
- * @param {boolean} required
20088
+ * @param {boolean} invalid
20045
20089
  * @protected
20046
20090
  */
20047
20091
  _invalidChanged(invalid) {
@@ -20241,13 +20285,23 @@ const InputMixin = dedupingMixin(
20241
20285
  observer: '_valueChanged',
20242
20286
  notify: true,
20243
20287
  },
20288
+
20289
+ /**
20290
+ * When true, the input element has a non-empty value entered by the user.
20291
+ * @protected
20292
+ */
20293
+ _hasInputValue: {
20294
+ type: Boolean,
20295
+ value: false,
20296
+ observer: '_hasInputValueChanged',
20297
+ },
20244
20298
  };
20245
20299
  }
20246
20300
 
20247
20301
  constructor() {
20248
20302
  super();
20249
20303
 
20250
- this._boundOnInput = this._onInput.bind(this);
20304
+ this._boundOnInput = this.__onInput.bind(this);
20251
20305
  this._boundOnChange = this._onChange.bind(this);
20252
20306
  }
20253
20307
 
@@ -20262,6 +20316,7 @@ const InputMixin = dedupingMixin(
20262
20316
  * Add event listeners to the input element instance.
20263
20317
  * Override this method to add custom listeners.
20264
20318
  * @param {!HTMLElement} input
20319
+ * @protected
20265
20320
  */
20266
20321
  _addInputListeners(input) {
20267
20322
  input.addEventListener('input', this._boundOnInput);
@@ -20271,6 +20326,7 @@ const InputMixin = dedupingMixin(
20271
20326
  /**
20272
20327
  * Remove event listeners from the input element instance.
20273
20328
  * @param {!HTMLElement} input
20329
+ * @protected
20274
20330
  */
20275
20331
  _removeInputListeners(input) {
20276
20332
  input.removeEventListener('input', this._boundOnInput);
@@ -20284,7 +20340,6 @@ const InputMixin = dedupingMixin(
20284
20340
  * for example to skip this in certain conditions.
20285
20341
  * @param {string} value
20286
20342
  * @protected
20287
- * @override
20288
20343
  */
20289
20344
  _forwardInputValue(value) {
20290
20345
  // Value might be set before an input element is initialized.
@@ -20301,7 +20356,11 @@ const InputMixin = dedupingMixin(
20301
20356
  }
20302
20357
  }
20303
20358
 
20304
- /** @protected */
20359
+ /**
20360
+ * @param {HTMLElement | undefined} input
20361
+ * @param {HTMLElement | undefined} oldInput
20362
+ * @protected
20363
+ */
20305
20364
  _inputElementChanged(input, oldInput) {
20306
20365
  if (input) {
20307
20366
  this._addInputListeners(input);
@@ -20310,17 +20369,47 @@ const InputMixin = dedupingMixin(
20310
20369
  }
20311
20370
  }
20312
20371
 
20372
+ /**
20373
+ * Observer to notify about the change of private property.
20374
+ *
20375
+ * @private
20376
+ */
20377
+ _hasInputValueChanged(hasValue, oldHasValue) {
20378
+ if (hasValue || oldHasValue) {
20379
+ this.dispatchEvent(new CustomEvent('has-input-value-changed'));
20380
+ }
20381
+ }
20382
+
20383
+ /**
20384
+ * An input event listener used to update `_hasInputValue` property.
20385
+ * Do not override this method.
20386
+ *
20387
+ * @param {Event} event
20388
+ * @private
20389
+ */
20390
+ __onInput(event) {
20391
+ // In the case a custom web component is passed as `inputElement`,
20392
+ // the actual native input element, on which the event occurred,
20393
+ // can be inside shadow trees.
20394
+ const target = event.composedPath()[0];
20395
+ this._hasInputValue = target.value.length > 0;
20396
+ this._onInput(event);
20397
+ }
20398
+
20313
20399
  /**
20314
20400
  * An input event listener used to update the field value.
20315
- * Override this method with an actual implementation.
20316
- * @param {Event} _event
20401
+ *
20402
+ * @param {Event} event
20317
20403
  * @protected
20318
- * @override
20319
20404
  */
20320
20405
  _onInput(event) {
20406
+ // In the case a custom web component is passed as `inputElement`,
20407
+ // the actual native input element, on which the event occurred,
20408
+ // can be inside shadow trees.
20409
+ const target = event.composedPath()[0];
20321
20410
  // Ignore fake input events e.g. used by clear button.
20322
20411
  this.__userInput = event.isTrusted;
20323
- this.value = event.target.value;
20412
+ this.value = target.value;
20324
20413
  this.__userInput = false;
20325
20414
  }
20326
20415
 
@@ -20329,12 +20418,12 @@ const InputMixin = dedupingMixin(
20329
20418
  * Override this method with an actual implementation.
20330
20419
  * @param {Event} _event
20331
20420
  * @protected
20332
- * @override
20333
20421
  */
20334
20422
  _onChange(_event) {}
20335
20423
 
20336
20424
  /**
20337
20425
  * Toggle the has-value attribute based on the value property.
20426
+ *
20338
20427
  * @param {boolean} hasValue
20339
20428
  * @protected
20340
20429
  */
@@ -20347,10 +20436,9 @@ const InputMixin = dedupingMixin(
20347
20436
  * @param {string | undefined} newVal
20348
20437
  * @param {string | undefined} oldVal
20349
20438
  * @protected
20350
- * @override
20351
20439
  */
20352
20440
  _valueChanged(newVal, oldVal) {
20353
- this._toggleHasValue(newVal !== '' && newVal != null);
20441
+ this._toggleHasValue(this._hasValue);
20354
20442
 
20355
20443
  // Setting initial value to empty string, do nothing.
20356
20444
  if (newVal === '' && oldVal === undefined) {
@@ -20365,6 +20453,16 @@ const InputMixin = dedupingMixin(
20365
20453
  // Setting a value programmatically, sync it to input element.
20366
20454
  this._forwardInputValue(newVal);
20367
20455
  }
20456
+
20457
+ /**
20458
+ * Indicates whether the value is different from the default one.
20459
+ * Override if the `value` property has a type other than `string`.
20460
+ *
20461
+ * @protected
20462
+ */
20463
+ get _hasValue() {
20464
+ return this.value != null && this.value !== '';
20465
+ }
20368
20466
  },
20369
20467
  );
20370
20468
 
@@ -20436,26 +20534,32 @@ const InputConstraintsMixin = dedupingMixin(
20436
20534
  _createConstraintsObserver() {
20437
20535
  // This complex observer needs to be added dynamically instead of using `static get observers()`
20438
20536
  // to make it possible to tweak this behavior in classes that apply this mixin.
20439
- this._createMethodObserver(`_constraintsChanged(${this.constructor.constraints.join(', ')})`);
20537
+ this._createMethodObserver(`_constraintsChanged(stateTarget, ${this.constructor.constraints.join(', ')})`);
20440
20538
  }
20441
20539
 
20442
20540
  /**
20443
20541
  * Override this method to implement custom validation constraints.
20542
+ * @param {HTMLElement | undefined} stateTarget
20444
20543
  * @param {unknown[]} constraints
20445
20544
  * @protected
20446
20545
  */
20447
- _constraintsChanged(...constraints) {
20448
- // Prevent marking field as invalid when setting required state
20449
- // or any other constraint before a user has entered the value.
20450
- if (!this.invalid) {
20546
+ _constraintsChanged(stateTarget, ...constraints) {
20547
+ // The input element's validity cannot be determined until
20548
+ // all the necessary constraint attributes aren't set on it.
20549
+ if (!stateTarget) {
20451
20550
  return;
20452
20551
  }
20453
20552
 
20454
- if (this._hasValidConstraints(constraints)) {
20553
+ const hasConstraints = this._hasValidConstraints(constraints);
20554
+ const isLastConstraintRemoved = this.__previousHasConstraints && !hasConstraints;
20555
+
20556
+ if ((this._hasValue || this.invalid) && hasConstraints) {
20455
20557
  this.validate();
20456
- } else {
20457
- this.invalid = false;
20558
+ } else if (isLastConstraintRemoved) {
20559
+ this._setInvalid(false);
20458
20560
  }
20561
+
20562
+ this.__previousHasConstraints = hasConstraints;
20459
20563
  }
20460
20564
 
20461
20565
  /**
@@ -20511,6 +20615,22 @@ const InputControlMixin = (superclass) =>
20511
20615
  ) {
20512
20616
  static get properties() {
20513
20617
  return {
20618
+ /**
20619
+ * A pattern matched against individual characters the user inputs.
20620
+ *
20621
+ * When set, the field will prevent:
20622
+ * - `keydown` events if the entered key doesn't match `/^allowedCharPattern$/`
20623
+ * - `paste` events if the pasted text doesn't match `/^allowedCharPattern*$/`
20624
+ * - `drop` events if the dropped text doesn't match `/^allowedCharPattern*$/`
20625
+ *
20626
+ * For example, to allow entering only numbers and minus signs, use:
20627
+ * `allowedCharPattern = "[\\d-]"`
20628
+ */
20629
+ allowedCharPattern: {
20630
+ type: String,
20631
+ observer: '_allowedCharPatternChanged',
20632
+ },
20633
+
20514
20634
  /**
20515
20635
  * If true, the input text gets fully selected when the field is focused using click or touch / tap.
20516
20636
  */
@@ -20568,6 +20688,14 @@ const InputControlMixin = (superclass) =>
20568
20688
  return [...super.delegateAttrs, 'name', 'type', 'placeholder', 'readonly', 'invalid', 'title'];
20569
20689
  }
20570
20690
 
20691
+ constructor() {
20692
+ super();
20693
+
20694
+ this._boundOnPaste = this._onPaste.bind(this);
20695
+ this._boundOnDrop = this._onDrop.bind(this);
20696
+ this._boundOnBeforeInput = this._onBeforeInput.bind(this);
20697
+ }
20698
+
20571
20699
  /**
20572
20700
  * Any element extending this mixin is required to implement this getter.
20573
20701
  * It returns the reference to the clear button element.
@@ -20660,6 +20788,115 @@ const InputControlMixin = (superclass) =>
20660
20788
  this.inputElement.dispatchEvent(new Event('change', { bubbles: true }));
20661
20789
  }
20662
20790
 
20791
+ /**
20792
+ * Override a method from `InputMixin`.
20793
+ * @param {!HTMLElement} input
20794
+ * @protected
20795
+ * @override
20796
+ */
20797
+ _addInputListeners(input) {
20798
+ super._addInputListeners(input);
20799
+
20800
+ input.addEventListener('paste', this._boundOnPaste);
20801
+ input.addEventListener('drop', this._boundOnDrop);
20802
+ input.addEventListener('beforeinput', this._boundOnBeforeInput);
20803
+ }
20804
+
20805
+ /**
20806
+ * Override a method from `InputMixin`.
20807
+ * @param {!HTMLElement} input
20808
+ * @protected
20809
+ * @override
20810
+ */
20811
+ _removeInputListeners(input) {
20812
+ super._removeInputListeners(input);
20813
+
20814
+ input.removeEventListener('paste', this._boundOnPaste);
20815
+ input.removeEventListener('drop', this._boundOnDrop);
20816
+ input.removeEventListener('beforeinput', this._boundOnBeforeInput);
20817
+ }
20818
+
20819
+ /**
20820
+ * Override an event listener from `KeyboardMixin`.
20821
+ * @param {!KeyboardEvent} event
20822
+ * @protected
20823
+ * @override
20824
+ */
20825
+ _onKeyDown(event) {
20826
+ super._onKeyDown(event);
20827
+
20828
+ if (this.allowedCharPattern && !this.__shouldAcceptKey(event)) {
20829
+ event.preventDefault();
20830
+ this._markInputPrevented();
20831
+ }
20832
+ }
20833
+
20834
+ /** @protected */
20835
+ _markInputPrevented() {
20836
+ // Add input-prevented attribute for 200ms
20837
+ this.setAttribute('input-prevented', '');
20838
+ this._preventInputDebouncer = Debouncer$1.debounce(this._preventInputDebouncer, timeOut.after(200), () => {
20839
+ this.removeAttribute('input-prevented');
20840
+ });
20841
+ }
20842
+
20843
+ /** @private */
20844
+ __shouldAcceptKey(event) {
20845
+ return (
20846
+ event.metaKey ||
20847
+ event.ctrlKey ||
20848
+ !event.key || // Allow typing anything if event.key is not supported
20849
+ event.key.length !== 1 || // Allow "Backspace", "ArrowLeft" etc.
20850
+ this.__allowedCharRegExp.test(event.key)
20851
+ );
20852
+ }
20853
+
20854
+ /** @private */
20855
+ _onPaste(e) {
20856
+ if (this.allowedCharPattern) {
20857
+ const pastedText = e.clipboardData.getData('text');
20858
+ if (!this.__allowedTextRegExp.test(pastedText)) {
20859
+ e.preventDefault();
20860
+ this._markInputPrevented();
20861
+ }
20862
+ }
20863
+ }
20864
+
20865
+ /** @private */
20866
+ _onDrop(e) {
20867
+ if (this.allowedCharPattern) {
20868
+ const draggedText = e.dataTransfer.getData('text');
20869
+ if (!this.__allowedTextRegExp.test(draggedText)) {
20870
+ e.preventDefault();
20871
+ this._markInputPrevented();
20872
+ }
20873
+ }
20874
+ }
20875
+
20876
+ /** @private */
20877
+ _onBeforeInput(e) {
20878
+ // The `beforeinput` event covers all the cases for `allowedCharPattern`: keyboard, pasting and dropping,
20879
+ // but it is still experimental technology so we can't rely on it. It's used here just as an additional check,
20880
+ // because it seems to be the only way to detect and prevent specific keys on mobile devices.
20881
+ // See https://github.com/vaadin/vaadin-text-field/issues/429
20882
+ if (this.allowedCharPattern && e.data && !this.__allowedTextRegExp.test(e.data)) {
20883
+ e.preventDefault();
20884
+ this._markInputPrevented();
20885
+ }
20886
+ }
20887
+
20888
+ /** @private */
20889
+ _allowedCharPatternChanged(charPattern) {
20890
+ if (charPattern) {
20891
+ try {
20892
+ this.__allowedCharRegExp = new RegExp(`^${charPattern}$`);
20893
+ this.__allowedTextRegExp = new RegExp(`^${charPattern}*$`);
20894
+ } catch (e) {
20895
+ console.error(e);
20896
+ }
20897
+ }
20898
+ }
20899
+
20663
20900
  /**
20664
20901
  * Fired when the user commits a value change.
20665
20902
  *
@@ -20698,14 +20935,13 @@ class InputController extends SlotController {
20698
20935
  }
20699
20936
 
20700
20937
  // Ensure every instance has unique ID
20701
- const uniqueId = (InputController._uniqueInputId = 1 + InputController._uniqueInputId || 0);
20702
- host._inputId = `${host.localName}-${uniqueId}`;
20703
- node.id = host._inputId;
20938
+ node.id = this.defaultId;
20704
20939
 
20705
20940
  if (typeof callback === 'function') {
20706
20941
  callback(node);
20707
20942
  }
20708
20943
  },
20944
+ true,
20709
20945
  );
20710
20946
  }
20711
20947
  }
@@ -20887,7 +21123,9 @@ class VirtualKeyboardController {
20887
21123
  * @param {function(new:HTMLElement)} subclass
20888
21124
  */
20889
21125
  const DatePickerMixin = (subclass) =>
20890
- class VaadinDatePickerMixin extends ControllerMixin(DelegateFocusMixin(InputMixin(KeyboardMixin(subclass)))) {
21126
+ class VaadinDatePickerMixin extends ControllerMixin(
21127
+ DelegateFocusMixin(InputConstraintsMixin(KeyboardMixin(subclass))),
21128
+ ) {
20891
21129
  static get properties() {
20892
21130
  return {
20893
21131
  /**
@@ -20916,7 +21154,6 @@ const DatePickerMixin = (subclass) =>
20916
21154
  */
20917
21155
  value: {
20918
21156
  type: String,
20919
- observer: '_valueChanged',
20920
21157
  notify: true,
20921
21158
  value: '',
20922
21159
  },
@@ -20972,13 +21209,6 @@ const DatePickerMixin = (subclass) =>
20972
21209
  value: '(max-width: 420px), (max-height: 420px)',
20973
21210
  },
20974
21211
 
20975
- /**
20976
- * An array of ancestor elements whose -webkit-overflow-scrolling is forced from value
20977
- * 'touch' to value 'auto' in order to prevent them from clipping the dropdown. iOS only.
20978
- * @private
20979
- */
20980
- _touchPrevented: Array,
20981
-
20982
21212
  /**
20983
21213
  * The object used to localize this component.
20984
21214
  * To change the default localization, replace the entire
@@ -21134,7 +21364,6 @@ const DatePickerMixin = (subclass) =>
21134
21364
  */
21135
21365
  min: {
21136
21366
  type: String,
21137
- observer: '_minChanged',
21138
21367
  },
21139
21368
 
21140
21369
  /**
@@ -21148,28 +21377,26 @@ const DatePickerMixin = (subclass) =>
21148
21377
  */
21149
21378
  max: {
21150
21379
  type: String,
21151
- observer: '_maxChanged',
21152
21380
  },
21153
21381
 
21154
21382
  /**
21155
21383
  * The earliest date that can be selected. All earlier dates will be disabled.
21156
- * @type {Date | string}
21384
+ * @type {Date | undefined}
21157
21385
  * @protected
21158
21386
  */
21159
21387
  _minDate: {
21160
21388
  type: Date,
21161
- // Null does not work here because minimizer passes undefined to overlay (#351)
21162
- value: '',
21389
+ computed: '__computeMinOrMaxDate(min)',
21163
21390
  },
21164
21391
 
21165
21392
  /**
21166
21393
  * The latest date that can be selected. All later dates will be disabled.
21167
- * @type {Date | string}
21394
+ * @type {Date | undefined}
21168
21395
  * @protected
21169
21396
  */
21170
21397
  _maxDate: {
21171
21398
  type: Date,
21172
- value: '',
21399
+ computed: '__computeMinOrMaxDate(max)',
21173
21400
  },
21174
21401
 
21175
21402
  /** @private */
@@ -21184,12 +21411,6 @@ const DatePickerMixin = (subclass) =>
21184
21411
  value: isIOS,
21185
21412
  },
21186
21413
 
21187
- /** @private */
21188
- _webkitOverflowScroll: {
21189
- type: Boolean,
21190
- value: document.createElement('div').style.webkitOverflowScrolling === '',
21191
- },
21192
-
21193
21414
  /** @private */
21194
21415
  _focusOverlayOnOpen: Boolean,
21195
21416
 
@@ -21205,6 +21426,10 @@ const DatePickerMixin = (subclass) =>
21205
21426
  ];
21206
21427
  }
21207
21428
 
21429
+ static get constraints() {
21430
+ return [...super.constraints, 'min', 'max'];
21431
+ }
21432
+
21208
21433
  /**
21209
21434
  * Override a getter from `InputControlMixin` to make it optional
21210
21435
  * and to prevent warning when a clear button is missing,
@@ -21271,12 +21496,10 @@ const DatePickerMixin = (subclass) =>
21271
21496
  }
21272
21497
  }
21273
21498
 
21274
- if (this.inputElement.value === '' && this.__dispatchChange) {
21275
- this.validate();
21499
+ this.validate();
21500
+
21501
+ if (this._inputValue === '' && this.value !== '') {
21276
21502
  this.value = '';
21277
- this.__dispatchChange = false;
21278
- } else {
21279
- this.validate();
21280
21503
  }
21281
21504
  }
21282
21505
  }
@@ -21345,14 +21568,19 @@ const DatePickerMixin = (subclass) =>
21345
21568
  this.$.overlay.removeAttribute('disable-upgrade');
21346
21569
  this._overlayInitialized = true;
21347
21570
 
21348
- this.$.overlay.addEventListener('opened-changed', (e) => (this.opened = e.detail.value));
21571
+ this.$.overlay.addEventListener('opened-changed', (e) => {
21572
+ this.opened = e.detail.value;
21573
+ });
21349
21574
 
21350
21575
  this.$.overlay.addEventListener('vaadin-overlay-escape-press', () => {
21351
21576
  this._focusedDate = this._selectedDate;
21352
21577
  this._close();
21353
21578
  });
21354
21579
 
21355
- this._overlayContent.addEventListener('close', this._close.bind(this));
21580
+ this._overlayContent.addEventListener('close', () => {
21581
+ this._close();
21582
+ });
21583
+
21356
21584
  this._overlayContent.addEventListener('focus-input', this._focusAndSelect.bind(this));
21357
21585
 
21358
21586
  // User confirmed selected date by clicking the calendar.
@@ -21361,7 +21589,7 @@ const DatePickerMixin = (subclass) =>
21361
21589
 
21362
21590
  this._selectDate(e.detail.date);
21363
21591
 
21364
- this._close(e);
21592
+ this._close();
21365
21593
  });
21366
21594
 
21367
21595
  // User confirmed selected date by pressing Enter or Today.
@@ -21371,24 +21599,18 @@ const DatePickerMixin = (subclass) =>
21371
21599
  this._selectDate(e.detail.date);
21372
21600
  });
21373
21601
 
21374
- // Keep focus attribute in focusElement for styling
21602
+ // Set focus-ring attribute when moving focus to the overlay
21603
+ // by pressing Tab or arrow key, after opening it on click.
21375
21604
  this._overlayContent.addEventListener('focusin', () => {
21376
- this._setFocused(true);
21605
+ if (this._keyboardActive) {
21606
+ this._setFocused(true);
21607
+ }
21377
21608
  });
21378
21609
 
21379
21610
  this.addEventListener('mousedown', () => this.__bringToFront());
21380
21611
  this.addEventListener('touchstart', () => this.__bringToFront());
21381
21612
  }
21382
21613
 
21383
- /**
21384
- * Returns true if `value` is valid, and sets the `invalid` flag appropriately.
21385
- *
21386
- * @return {boolean} True if the value is valid and sets the `invalid` flag appropriately
21387
- */
21388
- validate() {
21389
- return !(this.invalid = !this.checkValidity());
21390
- }
21391
-
21392
21614
  /**
21393
21615
  * Returns true if the current input value satisfies all constraints (if any)
21394
21616
  *
@@ -21399,7 +21621,7 @@ const DatePickerMixin = (subclass) =>
21399
21621
  checkValidity() {
21400
21622
  const inputValid =
21401
21623
  !this._inputValue ||
21402
- (this._selectedDate && this._inputValue === this._getFormattedDate(this.i18n.formatDate, this._selectedDate));
21624
+ (!!this._selectedDate && this._inputValue === this._getFormattedDate(this.i18n.formatDate, this._selectedDate));
21403
21625
  const minMaxValid = !this._selectedDate || dateAllowed(this._selectedDate, this._minDate, this._maxDate);
21404
21626
 
21405
21627
  let inputValidity = true;
@@ -21415,6 +21637,51 @@ const DatePickerMixin = (subclass) =>
21415
21637
  return inputValid && minMaxValid && inputValidity;
21416
21638
  }
21417
21639
 
21640
+ /**
21641
+ * Override method inherited from `FocusMixin`
21642
+ * to not call `_setFocused(true)` when focus
21643
+ * is restored after closing overlay on click,
21644
+ * and to avoid removing `focus-ring` attribute.
21645
+ *
21646
+ * @param {!FocusEvent} _event
21647
+ * @return {boolean}
21648
+ * @protected
21649
+ * @override
21650
+ */
21651
+ _shouldSetFocus(_event) {
21652
+ return !this._shouldKeepFocusRing;
21653
+ }
21654
+
21655
+ /**
21656
+ * Override method inherited from `FocusMixin`
21657
+ * to prevent removing the `focused` attribute:
21658
+ * - when moving focus to the overlay content,
21659
+ * - when closing on date click / outside click.
21660
+ *
21661
+ * @param {!FocusEvent} _event
21662
+ * @return {boolean}
21663
+ * @protected
21664
+ * @override
21665
+ */
21666
+ _shouldRemoveFocus(_event) {
21667
+ return !this.opened;
21668
+ }
21669
+
21670
+ /**
21671
+ * Override method inherited from `FocusMixin`
21672
+ * to store the `focus-ring` state to restore
21673
+ * it later when closing on outside click.
21674
+ *
21675
+ * @param {boolean} focused
21676
+ * @protected
21677
+ * @override
21678
+ */
21679
+ _setFocused(focused) {
21680
+ super._setFocused(focused);
21681
+
21682
+ this._shouldKeepFocusRing = focused && this._keyboardActive;
21683
+ }
21684
+
21418
21685
  /**
21419
21686
  * Select date on user interaction and set the flag
21420
21687
  * to fire change event if necessary.
@@ -21434,10 +21701,7 @@ const DatePickerMixin = (subclass) =>
21434
21701
  }
21435
21702
 
21436
21703
  /** @private */
21437
- _close(e) {
21438
- if (e) {
21439
- e.stopPropagation();
21440
- }
21704
+ _close() {
21441
21705
  this._focus();
21442
21706
  this.close();
21443
21707
  }
@@ -21566,47 +21830,46 @@ const DatePickerMixin = (subclass) =>
21566
21830
  }
21567
21831
  }
21568
21832
 
21569
- /** @private */
21570
- _handleDateChange(property, value, oldValue) {
21571
- if (!value) {
21572
- this[property] = '';
21573
- return;
21574
- }
21833
+ /**
21834
+ * Override the value observer from `InputMixin` to implement custom
21835
+ * handling of the `value` property. The date-picker doesn't forward
21836
+ * the value directly to the input like the default implementation of `InputMixin`.
21837
+ * Instead, it parses the value into a date, puts it in `_selectedDate` which
21838
+ * is then displayed in the input with respect to the specified date format.
21839
+ *
21840
+ * @param {string | undefined} value
21841
+ * @param {string | undefined} oldValue
21842
+ * @protected
21843
+ * @override
21844
+ */
21845
+ _valueChanged(value, oldValue) {
21846
+ const newDate = this._parseDate(value);
21575
21847
 
21576
- const date = this._parseDate(value);
21577
- if (!date) {
21848
+ if (value && !newDate) {
21849
+ // The new value cannot be parsed, revert the old value.
21578
21850
  this.value = oldValue;
21579
21851
  return;
21580
21852
  }
21581
- if (!dateEquals(this[property], date)) {
21582
- this[property] = date;
21583
- if (this.value) {
21584
- this.validate();
21585
- }
21586
- }
21587
- }
21588
21853
 
21589
- /** @private */
21590
- _valueChanged(value, oldValue) {
21591
- this._handleDateChange('_selectedDate', value, oldValue);
21592
-
21593
- this._toggleHasValue(!!value);
21594
- }
21854
+ if (value) {
21855
+ if (!dateEquals(this._selectedDate, newDate)) {
21856
+ // Update the date instance only if the date has actually changed.
21857
+ this._selectedDate = newDate;
21595
21858
 
21596
- /** @private */
21597
- _minChanged(value, oldValue) {
21598
- this._handleDateChange('_minDate', value, oldValue);
21599
- }
21859
+ if (oldValue !== undefined) {
21860
+ // Validate only if `value` changes after initialization.
21861
+ this.validate();
21862
+ }
21863
+ }
21864
+ } else {
21865
+ this._selectedDate = null;
21866
+ }
21600
21867
 
21601
- /** @private */
21602
- _maxChanged(value, oldValue) {
21603
- this._handleDateChange('_maxDate', value, oldValue);
21868
+ this._toggleHasValue(this._hasValue);
21604
21869
  }
21605
21870
 
21606
21871
  /** @protected */
21607
21872
  _onOverlayOpened() {
21608
- this._openedWithFocusRing = this.hasAttribute('focus-ring');
21609
-
21610
21873
  const parsedInitialPosition = this._parseDate(this.initialPosition);
21611
21874
 
21612
21875
  const initialPosition =
@@ -21626,10 +21889,6 @@ const DatePickerMixin = (subclass) =>
21626
21889
 
21627
21890
  window.addEventListener('scroll', this._boundOnScroll, true);
21628
21891
 
21629
- if (this._webkitOverflowScroll) {
21630
- this._touchPrevented = this._preventWebkitOverflowScrollingTouch(this.parentElement);
21631
- }
21632
-
21633
21892
  if (this._focusOverlayOnOpen) {
21634
21893
  this._overlayContent.focusDateElement();
21635
21894
  this._focusOverlayOnOpen = false;
@@ -21643,25 +21902,6 @@ const DatePickerMixin = (subclass) =>
21643
21902
  }
21644
21903
  }
21645
21904
 
21646
- // A hack needed for iOS to prevent dropdown from being clipped in an
21647
- // ancestor container with -webkit-overflow-scrolling: touch;
21648
- /** @private */
21649
- _preventWebkitOverflowScrollingTouch(element) {
21650
- const result = [];
21651
- while (element) {
21652
- if (window.getComputedStyle(element).webkitOverflowScrolling === 'touch') {
21653
- const oldInlineValue = element.style.webkitOverflowScrolling;
21654
- element.style.webkitOverflowScrolling = 'auto';
21655
- result.push({
21656
- element,
21657
- oldInlineValue,
21658
- });
21659
- }
21660
- element = element.parentElement;
21661
- }
21662
- return result;
21663
- }
21664
-
21665
21905
  /** @private */
21666
21906
  _selectParsedOrFocusedDate() {
21667
21907
  // Select the parsed input or focused date
@@ -21688,13 +21928,6 @@ const DatePickerMixin = (subclass) =>
21688
21928
  _onOverlayClosed() {
21689
21929
  window.removeEventListener('scroll', this._boundOnScroll, true);
21690
21930
 
21691
- if (this._touchPrevented) {
21692
- this._touchPrevented.forEach(
21693
- (prevented) => (prevented.element.style.webkitOverflowScrolling = prevented.oldInlineValue),
21694
- );
21695
- this._touchPrevented = [];
21696
- }
21697
-
21698
21931
  // No need to select date on close if it was confirmed by the user.
21699
21932
  if (this.__userConfirmedDate) {
21700
21933
  this.__userConfirmedDate = false;
@@ -21710,11 +21943,6 @@ const DatePickerMixin = (subclass) =>
21710
21943
  if (!this.value) {
21711
21944
  this.validate();
21712
21945
  }
21713
-
21714
- // If the input isn't focused when overlay closes (fullscreen mode), clear focused state
21715
- if (this.getRootNode().activeElement !== this.inputElement) {
21716
- this._setFocused(false);
21717
- }
21718
21946
  }
21719
21947
 
21720
21948
  /** @private */
@@ -21767,10 +21995,7 @@ const DatePickerMixin = (subclass) =>
21767
21995
  _onChange(event) {
21768
21996
  // For change event on the native <input> blur, after the input is cleared,
21769
21997
  // we schedule change event to be dispatched on date-picker blur.
21770
- if (
21771
- this.inputElement.value === '' &&
21772
- !(event.detail && event.detail.sourceEvent && event.detail.sourceEvent.__fromClearButton)
21773
- ) {
21998
+ if (this._inputValue === '') {
21774
21999
  this.__dispatchChange = true;
21775
22000
  }
21776
22001
 
@@ -21857,7 +22082,7 @@ const DatePickerMixin = (subclass) =>
21857
22082
  if (e.shiftKey) {
21858
22083
  this._overlayContent.focusCancel();
21859
22084
  } else {
21860
- this._overlayContent.focusDate(this._focusedDate);
22085
+ this._overlayContent.focusDateElement();
21861
22086
  }
21862
22087
  }
21863
22088
  break;
@@ -21968,6 +22193,11 @@ const DatePickerMixin = (subclass) =>
21968
22193
  return this.$.overlay.content.querySelector('#overlay-content');
21969
22194
  }
21970
22195
 
22196
+ /** @private */
22197
+ __computeMinOrMaxDate(dateString) {
22198
+ return this._parseDate(dateString);
22199
+ }
22200
+
21971
22201
  /**
21972
22202
  * Fired when the user commits a value change.
21973
22203
  *
@@ -22079,12 +22309,13 @@ registerStyles('vaadin-date-picker', [inputFieldShared, datePickerStyles], { mod
22079
22309
  * Note: the `theme` attribute value set on `<vaadin-date-picker>` is
22080
22310
  * propagated to the internal components listed above.
22081
22311
  *
22082
- * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
22312
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
22083
22313
  *
22084
22314
  * @fires {Event} change - Fired when the user commits a value change.
22085
22315
  * @fires {CustomEvent} invalid-changed - Fired when the `invalid` property changes.
22086
22316
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
22087
22317
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
22318
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
22088
22319
  *
22089
22320
  * @extends HTMLElement
22090
22321
  * @mixes ElementMixin
@@ -22138,7 +22369,7 @@ class DatePicker extends DatePickerMixin(InputControlMixin(ThemableMixin(Element
22138
22369
  fullscreen$="[[_fullscreen]]"
22139
22370
  theme$="[[__getOverlayTheme(_theme, _overlayInitialized)]]"
22140
22371
  on-vaadin-overlay-open="_onOverlayOpened"
22141
- on-vaadin-overlay-close="_onOverlayClosed"
22372
+ on-vaadin-overlay-closing="_onOverlayClosed"
22142
22373
  restore-focus-on-close
22143
22374
  restore-focus-node="[[inputElement]]"
22144
22375
  disable-upgrade
@@ -22198,11 +22429,6 @@ class DatePicker extends DatePickerMixin(InputControlMixin(ThemableMixin(Element
22198
22429
 
22199
22430
  /** @private */
22200
22431
  _onVaadinOverlayClose(e) {
22201
- if (this._openedWithFocusRing && this.hasAttribute('focused')) {
22202
- this.setAttribute('focus-ring', '');
22203
- } else if (!this.hasAttribute('focused')) {
22204
- this.blur();
22205
- }
22206
22432
  if (e.detail.sourceEvent && e.detail.sourceEvent.composedPath().includes(this)) {
22207
22433
  e.preventDefault();
22208
22434
  }