@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.
@@ -45,7 +45,7 @@ const translate$1 = (key, customLang) => {
45
45
  */
46
46
  class Lumo extends HTMLElement {
47
47
  static get version() {
48
- return '23.1.5';
48
+ return '23.2.0';
49
49
  }
50
50
  }
51
51
 
@@ -101,7 +101,7 @@ const ThemePropertyMixin = (superClass) =>
101
101
  * **NOTE:** Extending the mixin only provides the property for binding,
102
102
  * and does not make the propagation alone.
103
103
  *
104
- * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/ds/customization/styling-components/#sub-components).
104
+ * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components/#sub-components).
105
105
  * page for more information.
106
106
  *
107
107
  * @deprecated The `theme` property is not supposed for public use and will be dropped in Vaadin 24.
@@ -126,7 +126,7 @@ const ThemePropertyMixin = (superClass) =>
126
126
  * **NOTE:** Extending the mixin only provides the property for binding,
127
127
  * and does not make the propagation alone.
128
128
  *
129
- * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/ds/customization/styling-components/#sub-components).
129
+ * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components/#sub-components).
130
130
  * page for more information.
131
131
  *
132
132
  * @protected
@@ -231,9 +231,9 @@ function matchesThemeFor(themeFor, tagName) {
231
231
  */
232
232
  function getIncludePriority(moduleName = '') {
233
233
  let includePriority = 0;
234
- if (moduleName.indexOf('lumo-') === 0 || moduleName.indexOf('material-') === 0) {
234
+ if (moduleName.startsWith('lumo-') || moduleName.startsWith('material-')) {
235
235
  includePriority = 1;
236
- } else if (moduleName.indexOf('vaadin-') === 0) {
236
+ } else if (moduleName.startsWith('vaadin-')) {
237
237
  includePriority = 2;
238
238
  }
239
239
  return includePriority;
@@ -10118,6 +10118,38 @@ const ControllerMixin = dedupingMixin(
10118
10118
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
10119
10119
  */
10120
10120
 
10121
+ // We consider the keyboard to be active if the window has received a keydown
10122
+ // event since the last mousedown event.
10123
+ let keyboardActive = false;
10124
+
10125
+ // Listen for top-level keydown and mousedown events.
10126
+ // Use capture phase so we detect events even if they're handled.
10127
+ window.addEventListener(
10128
+ 'keydown',
10129
+ () => {
10130
+ keyboardActive = true;
10131
+ },
10132
+ { capture: true },
10133
+ );
10134
+
10135
+ window.addEventListener(
10136
+ 'mousedown',
10137
+ () => {
10138
+ keyboardActive = false;
10139
+ },
10140
+ { capture: true },
10141
+ );
10142
+
10143
+ /**
10144
+ * Returns true if the window has received a keydown
10145
+ * event since the last mousedown event.
10146
+ *
10147
+ * @return {boolean}
10148
+ */
10149
+ function isKeyboardActive() {
10150
+ return keyboardActive;
10151
+ }
10152
+
10121
10153
  /**
10122
10154
  * Returns true if the element is hidden directly with `display: none` or `visibility: hidden`,
10123
10155
  * false otherwise.
@@ -10558,7 +10590,7 @@ class FocusTrapController {
10558
10590
  * ---|---|---
10559
10591
  * `--vaadin-overlay-viewport-bottom` | Bottom offset of the visible viewport area | `0` or detected offset
10560
10592
  *
10561
- * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
10593
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
10562
10594
  *
10563
10595
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
10564
10596
  * @fires {CustomEvent} vaadin-overlay-open - Fired after the overlay is opened.
@@ -12828,7 +12860,7 @@ const registered = new Set();
12828
12860
  const ElementMixin = (superClass) =>
12829
12861
  class VaadinElementMixin extends DirMixin(superClass) {
12830
12862
  static get version() {
12831
- return '23.1.5';
12863
+ return '23.2.0';
12832
12864
  }
12833
12865
 
12834
12866
  /** @protected */
@@ -13126,7 +13158,7 @@ function _handleNative(ev) {
13126
13158
  }
13127
13159
  if (!ev[HANDLED_OBJ]) {
13128
13160
  ev[HANDLED_OBJ] = {};
13129
- if (type.slice(0, 5) === 'touch') {
13161
+ if (type.startsWith('touch')) {
13130
13162
  const t = ev.changedTouches[0];
13131
13163
  if (type === 'touchstart') {
13132
13164
  // Only handle the first finger
@@ -13985,28 +14017,6 @@ const ActiveMixin = (superclass) =>
13985
14017
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
13986
14018
  */
13987
14019
 
13988
- // We consider the keyboard to be active if the window has received a keydown
13989
- // event since the last mousedown event.
13990
- let keyboardActive = false;
13991
-
13992
- // Listen for top-level keydown and mousedown events.
13993
- // Use capture phase so we detect events even if they're handled.
13994
- window.addEventListener(
13995
- 'keydown',
13996
- () => {
13997
- keyboardActive = true;
13998
- },
13999
- { capture: true },
14000
- );
14001
-
14002
- window.addEventListener(
14003
- 'mousedown',
14004
- () => {
14005
- keyboardActive = false;
14006
- },
14007
- { capture: true },
14008
- );
14009
-
14010
14020
  /**
14011
14021
  * A mixin to handle `focused` and `focus-ring` attributes based on focus.
14012
14022
  *
@@ -14020,7 +14030,7 @@ const FocusMixin = dedupingMixin(
14020
14030
  * @return {boolean}
14021
14031
  */
14022
14032
  get _keyboardActive() {
14023
- return keyboardActive;
14033
+ return isKeyboardActive();
14024
14034
  }
14025
14035
 
14026
14036
  /** @protected */
@@ -14284,7 +14294,7 @@ const ButtonMixin = (superClass) =>
14284
14294
  * `focus-ring` | Set when the button is focused using the keyboard.
14285
14295
  * `focused` | Set when the button is focused.
14286
14296
  *
14287
- * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
14297
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
14288
14298
  *
14289
14299
  * @extends HTMLElement
14290
14300
  * @mixes ButtonMixin
@@ -14366,7 +14376,6 @@ registerStyles(
14366
14376
  i$1`
14367
14377
  :host {
14368
14378
  position: relative;
14369
- background-color: transparent;
14370
14379
  /* Background for the year scroller, placed here as we are using a mask image on the actual years part */
14371
14380
  background-image: linear-gradient(var(--lumo-shade-5pct), var(--lumo-shade-5pct));
14372
14381
  background-size: 57px 100%;
@@ -14474,17 +14483,10 @@ registerStyles(
14474
14483
 
14475
14484
  [part='toolbar'] {
14476
14485
  padding: var(--lumo-space-s);
14477
- box-shadow: 0 -1px 0 0 var(--lumo-contrast-10pct);
14478
14486
  border-bottom-left-radius: var(--lumo-border-radius-l);
14479
14487
  margin-right: 57px;
14480
14488
  }
14481
14489
 
14482
- @supports (mask-image: linear-gradient(#000, #000)) or (-webkit-mask-image: linear-gradient(#000, #000)) {
14483
- [part='toolbar'] {
14484
- box-shadow: none;
14485
- }
14486
- }
14487
-
14488
14490
  /* Today and Cancel buttons */
14489
14491
 
14490
14492
  [part='toolbar'] [part\$='button'] {
@@ -14517,8 +14519,6 @@ registerStyles(
14517
14519
  /* Very narrow screen (year scroller initially hidden) */
14518
14520
 
14519
14521
  [part='years-toggle-button'] {
14520
- position: relative;
14521
- right: auto;
14522
14522
  display: flex;
14523
14523
  align-items: center;
14524
14524
  height: var(--lumo-size-s);
@@ -14536,10 +14536,6 @@ registerStyles(
14536
14536
  color: var(--lumo-primary-contrast-color);
14537
14537
  }
14538
14538
 
14539
- [part='years-toggle-button']::before {
14540
- content: none;
14541
- }
14542
-
14543
14539
  /* TODO magic number (same as used for iron-media-query in vaadin-date-picker-overlay-content) */
14544
14540
  @media screen and (max-width: 374px) {
14545
14541
  :host {
@@ -14715,9 +14711,9 @@ registerStyles(
14715
14711
  { moduleId: 'lumo-month-calendar' },
14716
14712
  );
14717
14713
 
14718
- const $_documentContainer$1 = document.createElement('template');
14714
+ const template$1 = document.createElement('template');
14719
14715
 
14720
- $_documentContainer$1.innerHTML = `
14716
+ template$1.innerHTML = `
14721
14717
  <style>
14722
14718
  @keyframes vaadin-date-picker-month-calendar-focus-date {
14723
14719
  50% {
@@ -14727,7 +14723,7 @@ $_documentContainer$1.innerHTML = `
14727
14723
  </style>
14728
14724
  `;
14729
14725
 
14730
- document.head.appendChild($_documentContainer$1.content);
14726
+ document.head.appendChild(template$1.content);
14731
14727
 
14732
14728
  /**
14733
14729
  * @license
@@ -14735,9 +14731,9 @@ document.head.appendChild($_documentContainer$1.content);
14735
14731
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
14736
14732
  */
14737
14733
 
14738
- const $_documentContainer = document.createElement('template');
14734
+ const template = document.createElement('template');
14739
14735
 
14740
- $_documentContainer.innerHTML = `
14736
+ template.innerHTML = `
14741
14737
  <style>
14742
14738
  @font-face {
14743
14739
  font-family: 'lumo-icons';
@@ -14793,7 +14789,7 @@ $_documentContainer.innerHTML = `
14793
14789
  </style>
14794
14790
  `;
14795
14791
 
14796
- document.head.appendChild($_documentContainer.content);
14792
+ document.head.appendChild(template.content);
14797
14793
 
14798
14794
  /**
14799
14795
  * @license
@@ -15360,6 +15356,57 @@ function getAncestorRootNodes(node) {
15360
15356
  return result;
15361
15357
  }
15362
15358
 
15359
+ /**
15360
+ * @param {string} value
15361
+ * @return {Set<string>}
15362
+ */
15363
+ function deserializeAttributeValue(value) {
15364
+ if (!value) {
15365
+ return new Set();
15366
+ }
15367
+
15368
+ return new Set(value.split(' '));
15369
+ }
15370
+
15371
+ /**
15372
+ * @param {Set<string>} values
15373
+ * @return {string}
15374
+ */
15375
+ function serializeAttributeValue(values) {
15376
+ return [...values].join(' ');
15377
+ }
15378
+
15379
+ /**
15380
+ * Adds a value to an attribute containing space-delimited values.
15381
+ *
15382
+ * @param {HTMLElement} element
15383
+ * @param {string} attr
15384
+ * @param {string} value
15385
+ */
15386
+ function addValueToAttribute(element, attr, value) {
15387
+ const values = deserializeAttributeValue(element.getAttribute(attr));
15388
+ values.add(value);
15389
+ element.setAttribute(attr, serializeAttributeValue(values));
15390
+ }
15391
+
15392
+ /**
15393
+ * Removes a value from an attribute containing space-delimited values.
15394
+ * If the value is the last one, the whole attribute is removed.
15395
+ *
15396
+ * @param {HTMLElement} element
15397
+ * @param {string} attr
15398
+ * @param {string} value
15399
+ */
15400
+ function removeValueFromAttribute(element, attr, value) {
15401
+ const values = deserializeAttributeValue(element.getAttribute(attr));
15402
+ values.delete(value);
15403
+ if (values.size === 0) {
15404
+ element.removeAttribute(attr);
15405
+ return;
15406
+ }
15407
+ element.setAttribute(attr, serializeAttributeValue(values));
15408
+ }
15409
+
15363
15410
  /**
15364
15411
  * @license
15365
15412
  * Copyright (c) 2017 - 2022 Vaadin Ltd.
@@ -17084,7 +17131,9 @@ class MonthCalendar extends FocusMixin(ThemableMixin(PolymerElement)) {
17084
17131
 
17085
17132
  _onMonthGridTouchStart() {
17086
17133
  this._notTapping = false;
17087
- setTimeout(() => (this._notTapping = true), 300);
17134
+ setTimeout(() => {
17135
+ this._notTapping = true;
17136
+ }, 300);
17088
17137
  }
17089
17138
 
17090
17139
  _dateAdd(date, delta) {
@@ -17419,7 +17468,7 @@ class InfiniteScroller extends PolymerElement {
17419
17468
  // Once the first set of items start fading in, stamp the rest
17420
17469
  this._buffers.forEach((buffer) => {
17421
17470
  [].forEach.call(buffer.children, (insertionPoint) => this._ensureStampedInstance(insertionPoint._itemWrapper));
17422
- }, this);
17471
+ });
17423
17472
 
17424
17473
  if (!this._buffers[0].translateY) {
17425
17474
  this._reset();
@@ -17582,7 +17631,7 @@ class InfiniteScroller extends PolymerElement {
17582
17631
  }
17583
17632
  }, 1); // Wait for first reset
17584
17633
  }
17585
- }, this);
17634
+ });
17586
17635
 
17587
17636
  setTimeout(() => {
17588
17637
  afterNextRender(this, this._finishInit.bind(this));
@@ -17620,7 +17669,7 @@ class InfiniteScroller extends PolymerElement {
17620
17669
  });
17621
17670
  buffer.updated = true;
17622
17671
  }
17623
- }, this);
17672
+ });
17624
17673
  }
17625
17674
 
17626
17675
  _isVisible(element, container) {
@@ -17719,7 +17768,6 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
17719
17768
  height: 100%;
17720
17769
  width: 100%;
17721
17770
  outline: none;
17722
- background: #fff;
17723
17771
  }
17724
17772
 
17725
17773
  [part='overlay-header'] {
@@ -17737,22 +17785,14 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
17737
17785
  flex-grow: 1;
17738
17786
  }
17739
17787
 
17740
- [part='clear-button']:not([showclear]) {
17741
- display: none;
17788
+ [hidden] {
17789
+ display: none !important;
17742
17790
  }
17743
17791
 
17744
17792
  [part='years-toggle-button'] {
17745
17793
  display: flex;
17746
17794
  }
17747
17795
 
17748
- [part='years-toggle-button'][desktop] {
17749
- display: none;
17750
- }
17751
-
17752
- :host(:not([years-visible])) [part='years-toggle-button']::before {
17753
- transform: rotate(180deg);
17754
- }
17755
-
17756
17796
  #scrollers {
17757
17797
  display: flex;
17758
17798
  height: 100%;
@@ -17826,27 +17866,14 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
17826
17866
  z-index: 2;
17827
17867
  flex-shrink: 0;
17828
17868
  }
17829
-
17830
- [part~='overlay-header']:not([desktop]) {
17831
- padding-bottom: 40px;
17832
- }
17833
-
17834
- [part~='years-toggle-button'] {
17835
- position: absolute;
17836
- top: auto;
17837
- right: 8px;
17838
- bottom: 0;
17839
- z-index: 1;
17840
- padding: 8px;
17841
- }
17842
17869
  </style>
17843
17870
 
17844
17871
  <div part="overlay-header" on-touchend="_preventDefault" desktop$="[[_desktopMode]]" aria-hidden="true">
17845
17872
  <div part="label">[[_formatDisplayed(selectedDate, i18n.formatDate, label)]]</div>
17846
- <div part="clear-button" showclear$="[[_showClear(selectedDate)]]"></div>
17873
+ <div part="clear-button" hidden$="[[!selectedDate]]"></div>
17847
17874
  <div part="toggle-button"></div>
17848
17875
 
17849
- <div part="years-toggle-button" desktop$="[[_desktopMode]]" aria-hidden="true">
17876
+ <div part="years-toggle-button" hidden$="[[_desktopMode]]" aria-hidden="true">
17850
17877
  [[_yearAfterXMonths(_visibleMonthIndex)]]
17851
17878
  </div>
17852
17879
  </div>
@@ -17932,6 +17959,7 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
17932
17959
  */
17933
17960
  selectedDate: {
17934
17961
  type: Date,
17962
+ value: null,
17935
17963
  },
17936
17964
 
17937
17965
  /**
@@ -18007,10 +18035,12 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18007
18035
  return this.getAttribute('dir') === 'rtl';
18008
18036
  }
18009
18037
 
18038
+ get calendars() {
18039
+ return [...this.shadowRoot.querySelectorAll('vaadin-month-calendar')];
18040
+ }
18041
+
18010
18042
  get focusableDateElement() {
18011
- return [...this.shadowRoot.querySelectorAll('vaadin-month-calendar')]
18012
- .map((calendar) => calendar.focusableDateElement)
18013
- .find(Boolean);
18043
+ return this.calendars.map((calendar) => calendar.focusableDateElement).find(Boolean);
18014
18044
  }
18015
18045
 
18016
18046
  ready() {
@@ -18018,7 +18048,6 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18018
18048
 
18019
18049
  this.setAttribute('role', 'dialog');
18020
18050
 
18021
- addListener(this, 'tap', this._stopPropagation);
18022
18051
  addListener(this.$.scrollers, 'track', this._track.bind(this));
18023
18052
  addListener(this.shadowRoot.querySelector('[part="clear-button"]'), 'tap', this._clear.bind(this));
18024
18053
  addListener(this.shadowRoot.querySelector('[part="today-button"]'), 'tap', this._onTodayTap.bind(this));
@@ -18140,7 +18169,9 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18140
18169
 
18141
18170
  _onYearScrollTouchStart() {
18142
18171
  this._notTapping = false;
18143
- setTimeout(() => (this._notTapping = true), 300);
18172
+ setTimeout(() => {
18173
+ this._notTapping = true;
18174
+ }, 300);
18144
18175
 
18145
18176
  this._repositionMonthScroller();
18146
18177
  }
@@ -18151,7 +18182,9 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18151
18182
 
18152
18183
  _doIgnoreTaps() {
18153
18184
  this._ignoreTaps = true;
18154
- this._debouncer = Debouncer$1.debounce(this._debouncer, timeOut.after(300), () => (this._ignoreTaps = false));
18185
+ this._debouncer = Debouncer$1.debounce(this._debouncer, timeOut.after(300), () => {
18186
+ this._ignoreTaps = false;
18187
+ });
18155
18188
  }
18156
18189
 
18157
18190
  _formatDisplayed(date, formatDate, label) {
@@ -18182,10 +18215,6 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18182
18215
  this.scrollToDate(new Date(), true);
18183
18216
  }
18184
18217
 
18185
- _showClear(selectedDate) {
18186
- return !!selectedDate;
18187
- }
18188
-
18189
18218
  _onYearTap(e) {
18190
18219
  if (!this._ignoreTaps && !this._notTapping) {
18191
18220
  const scrollDelta =
@@ -18211,6 +18240,11 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18211
18240
 
18212
18241
  this._targetPosition = targetPosition;
18213
18242
 
18243
+ let revealResolve;
18244
+ this._revealPromise = new Promise((resolve) => {
18245
+ revealResolve = resolve;
18246
+ });
18247
+
18214
18248
  // http://gizma.com/easing/
18215
18249
  const easingFunction = (t, b, c, d) => {
18216
18250
  t /= d / 2;
@@ -18251,7 +18285,9 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18251
18285
 
18252
18286
  this.$.monthScroller.position = this._targetPosition;
18253
18287
  this._targetPosition = undefined;
18254
- this.__tryFocusDate();
18288
+
18289
+ revealResolve();
18290
+ this._revealPromise = undefined;
18255
18291
  }
18256
18292
 
18257
18293
  setTimeout(this._repositionYearScroller.bind(this), 1);
@@ -18461,51 +18497,44 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18461
18497
  switch (section) {
18462
18498
  case 'calendar':
18463
18499
  if (event.shiftKey) {
18464
- // Return focus back to the input field.
18465
18500
  event.preventDefault();
18466
- this.__focusInput();
18501
+
18502
+ if (this.hasAttribute('fullscreen')) {
18503
+ // Trap focus in the overlay
18504
+ this.$.cancelButton.focus();
18505
+ } else {
18506
+ this.__focusInput();
18507
+ }
18467
18508
  }
18468
18509
  break;
18469
18510
  case 'today':
18470
18511
  if (event.shiftKey) {
18471
- // Browser returns focus back to the calendar.
18472
- // We need to move the scroll to focused date.
18473
- setTimeout(() => this.revealDate(this.focusedDate), 1);
18512
+ event.preventDefault();
18513
+ this.focusDateElement();
18474
18514
  }
18475
18515
  break;
18476
18516
  case 'cancel':
18477
18517
  if (!event.shiftKey) {
18478
- // Return focus back to the input field.
18479
18518
  event.preventDefault();
18480
- this.__focusInput();
18519
+
18520
+ if (this.hasAttribute('fullscreen')) {
18521
+ // Trap focus in the overlay
18522
+ this.focusDateElement();
18523
+ } else {
18524
+ this.__focusInput();
18525
+ }
18481
18526
  }
18482
18527
  break;
18483
18528
  }
18484
18529
  }
18485
18530
 
18486
18531
  __onTodayButtonKeyDown(event) {
18487
- if (this.hasAttribute('fullscreen')) {
18488
- // Do not prevent closing on Esc
18489
- if (event.key !== 'Escape') {
18490
- event.stopPropagation();
18491
- }
18492
- return;
18493
- }
18494
-
18495
18532
  if (event.key === 'Tab') {
18496
18533
  this._onTabKeyDown(event, 'today');
18497
18534
  }
18498
18535
  }
18499
18536
 
18500
18537
  __onCancelButtonKeyDown(event) {
18501
- if (this.hasAttribute('fullscreen')) {
18502
- // Do not prevent closing on Esc
18503
- if (event.key !== 'Escape') {
18504
- event.stopPropagation();
18505
- }
18506
- return;
18507
- }
18508
-
18509
18538
  if (event.key === 'Tab') {
18510
18539
  this._onTabKeyDown(event, 'cancel');
18511
18540
  }
@@ -18534,15 +18563,29 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18534
18563
  if (!keepMonth) {
18535
18564
  this._focusedMonthDate = dateToFocus.getDate();
18536
18565
  }
18537
- await this.focusDateElement();
18566
+ await this.focusDateElement(false);
18538
18567
  }
18539
18568
 
18540
- async focusDateElement() {
18569
+ async focusDateElement(reveal = true) {
18541
18570
  this.__pendingDateFocus = this.focusedDate;
18542
18571
 
18543
- await new Promise((resolve) => {
18544
- requestAnimationFrame(resolve);
18545
- });
18572
+ // Wait for `vaadin-month-calendar` elements to be rendered
18573
+ if (!this.calendars.length) {
18574
+ await new Promise((resolve) => {
18575
+ setTimeout(resolve);
18576
+ });
18577
+ }
18578
+
18579
+ // Reveal focused date unless it has been just set,
18580
+ // which triggers `revealDate()` in the observer.
18581
+ if (reveal) {
18582
+ this.revealDate(this.focusedDate);
18583
+ }
18584
+
18585
+ if (this._revealPromise) {
18586
+ // Wait for focused date to be scrolled into view.
18587
+ await this._revealPromise;
18588
+ }
18546
18589
 
18547
18590
  this.__tryFocusDate();
18548
18591
  }
@@ -18640,10 +18683,6 @@ class DatePickerOverlayContent extends ControllerMixin(ThemableMixin(DirMixin(Po
18640
18683
  todayMidnight.setDate(today.getDate());
18641
18684
  return this._dateAllowed(todayMidnight, min, max);
18642
18685
  }
18643
-
18644
- _stopPropagation(e) {
18645
- e.stopPropagation();
18646
- }
18647
18686
  }
18648
18687
 
18649
18688
  customElements.define(DatePickerOverlayContent.is, DatePickerOverlayContent);
@@ -18874,6 +18913,24 @@ const DelegateFocusMixin = dedupingMixin(
18874
18913
  },
18875
18914
  );
18876
18915
 
18916
+ /**
18917
+ * @license
18918
+ * Copyright (c) 2021 - 2022 Vaadin Ltd.
18919
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
18920
+ */
18921
+
18922
+ let uniqueId = 0;
18923
+
18924
+ /**
18925
+ * Returns a unique integer id.
18926
+ *
18927
+ * @return {number}
18928
+ */
18929
+ function generateUniqueId() {
18930
+ // eslint-disable-next-line no-plusplus
18931
+ return uniqueId++;
18932
+ }
18933
+
18877
18934
  /**
18878
18935
  * @license
18879
18936
  * Copyright (c) 2021 - 2022 Vaadin Ltd.
@@ -18894,24 +18951,21 @@ class SlotController extends EventTarget {
18894
18951
  */
18895
18952
  static generateId(slotName, host) {
18896
18953
  const prefix = slotName || 'default';
18897
-
18898
- // Support dash-case slot names e.g. "error-message"
18899
- const field = `${dashToCamelCase(prefix)}Id`;
18900
-
18901
- // Maintain the unique ID counter for a given prefix.
18902
- this[field] = 1 + this[field] || 0;
18903
-
18904
- return `${prefix}-${host.localName}-${this[field]}`;
18954
+ return `${prefix}-${host.localName}-${generateUniqueId()}`;
18905
18955
  }
18906
18956
 
18907
- constructor(host, slotName, slotFactory, slotInitializer) {
18957
+ constructor(host, slotName, slotFactory, slotInitializer, useUniqueId) {
18908
18958
  super();
18909
18959
 
18910
18960
  this.host = host;
18911
18961
  this.slotName = slotName;
18912
18962
  this.slotFactory = slotFactory;
18913
18963
  this.slotInitializer = slotInitializer;
18914
- this.defaultId = SlotController.generateId(slotName, host);
18964
+
18965
+ // Only generate the default ID if requested by the controller.
18966
+ if (useUniqueId) {
18967
+ this.defaultId = SlotController.generateId(slotName, host);
18968
+ }
18915
18969
  }
18916
18970
 
18917
18971
  hostConnected() {
@@ -19066,6 +19120,7 @@ class ErrorController extends SlotController {
19066
19120
 
19067
19121
  this.__updateHasError();
19068
19122
  },
19123
+ true,
19069
19124
  );
19070
19125
  }
19071
19126
 
@@ -19180,63 +19235,6 @@ class ErrorController extends SlotController {
19180
19235
  }
19181
19236
  }
19182
19237
 
19183
- /**
19184
- * @license
19185
- * Copyright (c) 2021 - 2022 Vaadin Ltd.
19186
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
19187
- */
19188
-
19189
- /**
19190
- * @param {string} value
19191
- * @return {Set<string>}
19192
- */
19193
- function deserializeAttributeValue(value) {
19194
- if (!value) {
19195
- return new Set();
19196
- }
19197
-
19198
- return new Set(value.split(' '));
19199
- }
19200
-
19201
- /**
19202
- * @param {Set<string>} values
19203
- * @return {string}
19204
- */
19205
- function serializeAttributeValue(values) {
19206
- return [...values].join(' ');
19207
- }
19208
-
19209
- /**
19210
- * Adds a value to an attribute containing space-delimited values.
19211
- *
19212
- * @param {HTMLElement} element
19213
- * @param {string} attr
19214
- * @param {string} value
19215
- */
19216
- function addValueToAttribute(element, attr, value) {
19217
- const values = deserializeAttributeValue(element.getAttribute(attr));
19218
- values.add(value);
19219
- element.setAttribute(attr, serializeAttributeValue(values));
19220
- }
19221
-
19222
- /**
19223
- * Removes a value from an attribute containing space-delimited values.
19224
- * If the value is the last one, the whole attribute is removed.
19225
- *
19226
- * @param {HTMLElement} element
19227
- * @param {string} attr
19228
- * @param {string} value
19229
- */
19230
- function removeValueFromAttribute(element, attr, value) {
19231
- const values = deserializeAttributeValue(element.getAttribute(attr));
19232
- values.delete(value);
19233
- if (values.size === 0) {
19234
- element.removeAttribute(attr);
19235
- return;
19236
- }
19237
- element.setAttribute(attr, serializeAttributeValue(values));
19238
- }
19239
-
19240
19238
  /**
19241
19239
  * @license
19242
19240
  * Copyright (c) 2021 - 2022 Vaadin Ltd.
@@ -19411,7 +19409,7 @@ class FieldAriaController {
19411
19409
 
19412
19410
  /**
19413
19411
  * @license
19414
- * Copyright (c) 2021 Vaadin Ltd.
19412
+ * Copyright (c) 2021 - 2022 Vaadin Ltd.
19415
19413
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
19416
19414
  */
19417
19415
 
@@ -19421,7 +19419,7 @@ class FieldAriaController {
19421
19419
  class HelperController extends SlotController {
19422
19420
  constructor(host) {
19423
19421
  // Do not provide slot factory, as only create helper lazily.
19424
- super(host, 'helper');
19422
+ super(host, 'helper', null, null, true);
19425
19423
  }
19426
19424
 
19427
19425
  get helperId() {
@@ -19618,6 +19616,7 @@ class LabelController extends SlotController {
19618
19616
 
19619
19617
  this.__observeLabel(node);
19620
19618
  },
19619
+ true,
19621
19620
  );
19622
19621
  }
19623
19622
 
@@ -19824,6 +19823,12 @@ const LabelMixin = dedupingMixin(
19824
19823
  super();
19825
19824
 
19826
19825
  this._labelController = new LabelController(this);
19826
+ }
19827
+
19828
+ /** @protected */
19829
+ ready() {
19830
+ super.ready();
19831
+
19827
19832
  this.addController(this._labelController);
19828
19833
  }
19829
19834
 
@@ -19871,12 +19876,17 @@ const ValidateMixin = dedupingMixin(
19871
19876
  }
19872
19877
 
19873
19878
  /**
19874
- * Returns true if field is valid, and sets `invalid` based on the field validity.
19879
+ * Validates the field and sets the `invalid` property based on the result.
19880
+ *
19881
+ * The method fires a `validated` event with the result of the validation.
19875
19882
  *
19876
19883
  * @return {boolean} True if the value is valid.
19877
19884
  */
19878
19885
  validate() {
19879
- return !(this.invalid = !this.checkValidity());
19886
+ const isValid = this.checkValidity();
19887
+ this._setInvalid(!isValid);
19888
+ this.dispatchEvent(new CustomEvent('validated', { detail: { valid: isValid } }));
19889
+ return isValid;
19880
19890
  }
19881
19891
 
19882
19892
  /**
@@ -19887,6 +19897,35 @@ const ValidateMixin = dedupingMixin(
19887
19897
  checkValidity() {
19888
19898
  return !this.required || !!this.value;
19889
19899
  }
19900
+
19901
+ /**
19902
+ * @param {boolean} invalid
19903
+ * @protected
19904
+ */
19905
+ _setInvalid(invalid) {
19906
+ if (this._shouldSetInvalid(invalid)) {
19907
+ this.invalid = invalid;
19908
+ }
19909
+ }
19910
+
19911
+ /**
19912
+ * Override this method to define whether the given `invalid` state should be set.
19913
+ *
19914
+ * @param {boolean} _invalid
19915
+ * @return {boolean}
19916
+ * @protected
19917
+ */
19918
+ _shouldSetInvalid(_invalid) {
19919
+ return true;
19920
+ }
19921
+
19922
+ /**
19923
+ * Fired whenever the field is validated.
19924
+ *
19925
+ * @event validated
19926
+ * @param {Object} detail
19927
+ * @param {boolean} detail.valid the result of the validation.
19928
+ */
19890
19929
  },
19891
19930
  );
19892
19931
 
@@ -19975,10 +20014,6 @@ const FieldMixin = (superclass) =>
19975
20014
  this._helperController = new HelperController(this);
19976
20015
  this._errorController = new ErrorController(this);
19977
20016
 
19978
- this.addController(this._fieldAriaController);
19979
- this.addController(this._helperController);
19980
- this.addController(this._errorController);
19981
-
19982
20017
  this._labelController.addEventListener('label-changed', (event) => {
19983
20018
  const { hasLabel, node } = event.detail;
19984
20019
  this.__labelChanged(hasLabel, node);
@@ -19990,6 +20025,15 @@ const FieldMixin = (superclass) =>
19990
20025
  });
19991
20026
  }
19992
20027
 
20028
+ /** @protected */
20029
+ ready() {
20030
+ super.ready();
20031
+
20032
+ this.addController(this._fieldAriaController);
20033
+ this.addController(this._helperController);
20034
+ this.addController(this._errorController);
20035
+ }
20036
+
19993
20037
  /** @private */
19994
20038
  __helperChanged(hasHelper, helperNode) {
19995
20039
  if (hasHelper) {
@@ -20045,7 +20089,7 @@ const FieldMixin = (superclass) =>
20045
20089
  }
20046
20090
 
20047
20091
  /**
20048
- * @param {boolean} required
20092
+ * @param {boolean} invalid
20049
20093
  * @protected
20050
20094
  */
20051
20095
  _invalidChanged(invalid) {
@@ -20245,13 +20289,23 @@ const InputMixin = dedupingMixin(
20245
20289
  observer: '_valueChanged',
20246
20290
  notify: true,
20247
20291
  },
20292
+
20293
+ /**
20294
+ * When true, the input element has a non-empty value entered by the user.
20295
+ * @protected
20296
+ */
20297
+ _hasInputValue: {
20298
+ type: Boolean,
20299
+ value: false,
20300
+ observer: '_hasInputValueChanged',
20301
+ },
20248
20302
  };
20249
20303
  }
20250
20304
 
20251
20305
  constructor() {
20252
20306
  super();
20253
20307
 
20254
- this._boundOnInput = this._onInput.bind(this);
20308
+ this._boundOnInput = this.__onInput.bind(this);
20255
20309
  this._boundOnChange = this._onChange.bind(this);
20256
20310
  }
20257
20311
 
@@ -20266,6 +20320,7 @@ const InputMixin = dedupingMixin(
20266
20320
  * Add event listeners to the input element instance.
20267
20321
  * Override this method to add custom listeners.
20268
20322
  * @param {!HTMLElement} input
20323
+ * @protected
20269
20324
  */
20270
20325
  _addInputListeners(input) {
20271
20326
  input.addEventListener('input', this._boundOnInput);
@@ -20275,6 +20330,7 @@ const InputMixin = dedupingMixin(
20275
20330
  /**
20276
20331
  * Remove event listeners from the input element instance.
20277
20332
  * @param {!HTMLElement} input
20333
+ * @protected
20278
20334
  */
20279
20335
  _removeInputListeners(input) {
20280
20336
  input.removeEventListener('input', this._boundOnInput);
@@ -20288,7 +20344,6 @@ const InputMixin = dedupingMixin(
20288
20344
  * for example to skip this in certain conditions.
20289
20345
  * @param {string} value
20290
20346
  * @protected
20291
- * @override
20292
20347
  */
20293
20348
  _forwardInputValue(value) {
20294
20349
  // Value might be set before an input element is initialized.
@@ -20305,7 +20360,11 @@ const InputMixin = dedupingMixin(
20305
20360
  }
20306
20361
  }
20307
20362
 
20308
- /** @protected */
20363
+ /**
20364
+ * @param {HTMLElement | undefined} input
20365
+ * @param {HTMLElement | undefined} oldInput
20366
+ * @protected
20367
+ */
20309
20368
  _inputElementChanged(input, oldInput) {
20310
20369
  if (input) {
20311
20370
  this._addInputListeners(input);
@@ -20314,17 +20373,47 @@ const InputMixin = dedupingMixin(
20314
20373
  }
20315
20374
  }
20316
20375
 
20376
+ /**
20377
+ * Observer to notify about the change of private property.
20378
+ *
20379
+ * @private
20380
+ */
20381
+ _hasInputValueChanged(hasValue, oldHasValue) {
20382
+ if (hasValue || oldHasValue) {
20383
+ this.dispatchEvent(new CustomEvent('has-input-value-changed'));
20384
+ }
20385
+ }
20386
+
20387
+ /**
20388
+ * An input event listener used to update `_hasInputValue` property.
20389
+ * Do not override this method.
20390
+ *
20391
+ * @param {Event} event
20392
+ * @private
20393
+ */
20394
+ __onInput(event) {
20395
+ // In the case a custom web component is passed as `inputElement`,
20396
+ // the actual native input element, on which the event occurred,
20397
+ // can be inside shadow trees.
20398
+ const target = event.composedPath()[0];
20399
+ this._hasInputValue = target.value.length > 0;
20400
+ this._onInput(event);
20401
+ }
20402
+
20317
20403
  /**
20318
20404
  * An input event listener used to update the field value.
20319
- * Override this method with an actual implementation.
20320
- * @param {Event} _event
20405
+ *
20406
+ * @param {Event} event
20321
20407
  * @protected
20322
- * @override
20323
20408
  */
20324
20409
  _onInput(event) {
20410
+ // In the case a custom web component is passed as `inputElement`,
20411
+ // the actual native input element, on which the event occurred,
20412
+ // can be inside shadow trees.
20413
+ const target = event.composedPath()[0];
20325
20414
  // Ignore fake input events e.g. used by clear button.
20326
20415
  this.__userInput = event.isTrusted;
20327
- this.value = event.target.value;
20416
+ this.value = target.value;
20328
20417
  this.__userInput = false;
20329
20418
  }
20330
20419
 
@@ -20333,12 +20422,12 @@ const InputMixin = dedupingMixin(
20333
20422
  * Override this method with an actual implementation.
20334
20423
  * @param {Event} _event
20335
20424
  * @protected
20336
- * @override
20337
20425
  */
20338
20426
  _onChange(_event) {}
20339
20427
 
20340
20428
  /**
20341
20429
  * Toggle the has-value attribute based on the value property.
20430
+ *
20342
20431
  * @param {boolean} hasValue
20343
20432
  * @protected
20344
20433
  */
@@ -20351,10 +20440,9 @@ const InputMixin = dedupingMixin(
20351
20440
  * @param {string | undefined} newVal
20352
20441
  * @param {string | undefined} oldVal
20353
20442
  * @protected
20354
- * @override
20355
20443
  */
20356
20444
  _valueChanged(newVal, oldVal) {
20357
- this._toggleHasValue(newVal !== '' && newVal != null);
20445
+ this._toggleHasValue(this._hasValue);
20358
20446
 
20359
20447
  // Setting initial value to empty string, do nothing.
20360
20448
  if (newVal === '' && oldVal === undefined) {
@@ -20369,6 +20457,16 @@ const InputMixin = dedupingMixin(
20369
20457
  // Setting a value programmatically, sync it to input element.
20370
20458
  this._forwardInputValue(newVal);
20371
20459
  }
20460
+
20461
+ /**
20462
+ * Indicates whether the value is different from the default one.
20463
+ * Override if the `value` property has a type other than `string`.
20464
+ *
20465
+ * @protected
20466
+ */
20467
+ get _hasValue() {
20468
+ return this.value != null && this.value !== '';
20469
+ }
20372
20470
  },
20373
20471
  );
20374
20472
 
@@ -20440,26 +20538,32 @@ const InputConstraintsMixin = dedupingMixin(
20440
20538
  _createConstraintsObserver() {
20441
20539
  // This complex observer needs to be added dynamically instead of using `static get observers()`
20442
20540
  // to make it possible to tweak this behavior in classes that apply this mixin.
20443
- this._createMethodObserver(`_constraintsChanged(${this.constructor.constraints.join(', ')})`);
20541
+ this._createMethodObserver(`_constraintsChanged(stateTarget, ${this.constructor.constraints.join(', ')})`);
20444
20542
  }
20445
20543
 
20446
20544
  /**
20447
20545
  * Override this method to implement custom validation constraints.
20546
+ * @param {HTMLElement | undefined} stateTarget
20448
20547
  * @param {unknown[]} constraints
20449
20548
  * @protected
20450
20549
  */
20451
- _constraintsChanged(...constraints) {
20452
- // Prevent marking field as invalid when setting required state
20453
- // or any other constraint before a user has entered the value.
20454
- if (!this.invalid) {
20550
+ _constraintsChanged(stateTarget, ...constraints) {
20551
+ // The input element's validity cannot be determined until
20552
+ // all the necessary constraint attributes aren't set on it.
20553
+ if (!stateTarget) {
20455
20554
  return;
20456
20555
  }
20457
20556
 
20458
- if (this._hasValidConstraints(constraints)) {
20557
+ const hasConstraints = this._hasValidConstraints(constraints);
20558
+ const isLastConstraintRemoved = this.__previousHasConstraints && !hasConstraints;
20559
+
20560
+ if ((this._hasValue || this.invalid) && hasConstraints) {
20459
20561
  this.validate();
20460
- } else {
20461
- this.invalid = false;
20562
+ } else if (isLastConstraintRemoved) {
20563
+ this._setInvalid(false);
20462
20564
  }
20565
+
20566
+ this.__previousHasConstraints = hasConstraints;
20463
20567
  }
20464
20568
 
20465
20569
  /**
@@ -20515,6 +20619,22 @@ const InputControlMixin = (superclass) =>
20515
20619
  ) {
20516
20620
  static get properties() {
20517
20621
  return {
20622
+ /**
20623
+ * A pattern matched against individual characters the user inputs.
20624
+ *
20625
+ * When set, the field will prevent:
20626
+ * - `keydown` events if the entered key doesn't match `/^allowedCharPattern$/`
20627
+ * - `paste` events if the pasted text doesn't match `/^allowedCharPattern*$/`
20628
+ * - `drop` events if the dropped text doesn't match `/^allowedCharPattern*$/`
20629
+ *
20630
+ * For example, to allow entering only numbers and minus signs, use:
20631
+ * `allowedCharPattern = "[\\d-]"`
20632
+ */
20633
+ allowedCharPattern: {
20634
+ type: String,
20635
+ observer: '_allowedCharPatternChanged',
20636
+ },
20637
+
20518
20638
  /**
20519
20639
  * If true, the input text gets fully selected when the field is focused using click or touch / tap.
20520
20640
  */
@@ -20572,6 +20692,14 @@ const InputControlMixin = (superclass) =>
20572
20692
  return [...super.delegateAttrs, 'name', 'type', 'placeholder', 'readonly', 'invalid', 'title'];
20573
20693
  }
20574
20694
 
20695
+ constructor() {
20696
+ super();
20697
+
20698
+ this._boundOnPaste = this._onPaste.bind(this);
20699
+ this._boundOnDrop = this._onDrop.bind(this);
20700
+ this._boundOnBeforeInput = this._onBeforeInput.bind(this);
20701
+ }
20702
+
20575
20703
  /**
20576
20704
  * Any element extending this mixin is required to implement this getter.
20577
20705
  * It returns the reference to the clear button element.
@@ -20664,6 +20792,115 @@ const InputControlMixin = (superclass) =>
20664
20792
  this.inputElement.dispatchEvent(new Event('change', { bubbles: true }));
20665
20793
  }
20666
20794
 
20795
+ /**
20796
+ * Override a method from `InputMixin`.
20797
+ * @param {!HTMLElement} input
20798
+ * @protected
20799
+ * @override
20800
+ */
20801
+ _addInputListeners(input) {
20802
+ super._addInputListeners(input);
20803
+
20804
+ input.addEventListener('paste', this._boundOnPaste);
20805
+ input.addEventListener('drop', this._boundOnDrop);
20806
+ input.addEventListener('beforeinput', this._boundOnBeforeInput);
20807
+ }
20808
+
20809
+ /**
20810
+ * Override a method from `InputMixin`.
20811
+ * @param {!HTMLElement} input
20812
+ * @protected
20813
+ * @override
20814
+ */
20815
+ _removeInputListeners(input) {
20816
+ super._removeInputListeners(input);
20817
+
20818
+ input.removeEventListener('paste', this._boundOnPaste);
20819
+ input.removeEventListener('drop', this._boundOnDrop);
20820
+ input.removeEventListener('beforeinput', this._boundOnBeforeInput);
20821
+ }
20822
+
20823
+ /**
20824
+ * Override an event listener from `KeyboardMixin`.
20825
+ * @param {!KeyboardEvent} event
20826
+ * @protected
20827
+ * @override
20828
+ */
20829
+ _onKeyDown(event) {
20830
+ super._onKeyDown(event);
20831
+
20832
+ if (this.allowedCharPattern && !this.__shouldAcceptKey(event)) {
20833
+ event.preventDefault();
20834
+ this._markInputPrevented();
20835
+ }
20836
+ }
20837
+
20838
+ /** @protected */
20839
+ _markInputPrevented() {
20840
+ // Add input-prevented attribute for 200ms
20841
+ this.setAttribute('input-prevented', '');
20842
+ this._preventInputDebouncer = Debouncer$1.debounce(this._preventInputDebouncer, timeOut.after(200), () => {
20843
+ this.removeAttribute('input-prevented');
20844
+ });
20845
+ }
20846
+
20847
+ /** @private */
20848
+ __shouldAcceptKey(event) {
20849
+ return (
20850
+ event.metaKey ||
20851
+ event.ctrlKey ||
20852
+ !event.key || // Allow typing anything if event.key is not supported
20853
+ event.key.length !== 1 || // Allow "Backspace", "ArrowLeft" etc.
20854
+ this.__allowedCharRegExp.test(event.key)
20855
+ );
20856
+ }
20857
+
20858
+ /** @private */
20859
+ _onPaste(e) {
20860
+ if (this.allowedCharPattern) {
20861
+ const pastedText = e.clipboardData.getData('text');
20862
+ if (!this.__allowedTextRegExp.test(pastedText)) {
20863
+ e.preventDefault();
20864
+ this._markInputPrevented();
20865
+ }
20866
+ }
20867
+ }
20868
+
20869
+ /** @private */
20870
+ _onDrop(e) {
20871
+ if (this.allowedCharPattern) {
20872
+ const draggedText = e.dataTransfer.getData('text');
20873
+ if (!this.__allowedTextRegExp.test(draggedText)) {
20874
+ e.preventDefault();
20875
+ this._markInputPrevented();
20876
+ }
20877
+ }
20878
+ }
20879
+
20880
+ /** @private */
20881
+ _onBeforeInput(e) {
20882
+ // The `beforeinput` event covers all the cases for `allowedCharPattern`: keyboard, pasting and dropping,
20883
+ // but it is still experimental technology so we can't rely on it. It's used here just as an additional check,
20884
+ // because it seems to be the only way to detect and prevent specific keys on mobile devices.
20885
+ // See https://github.com/vaadin/vaadin-text-field/issues/429
20886
+ if (this.allowedCharPattern && e.data && !this.__allowedTextRegExp.test(e.data)) {
20887
+ e.preventDefault();
20888
+ this._markInputPrevented();
20889
+ }
20890
+ }
20891
+
20892
+ /** @private */
20893
+ _allowedCharPatternChanged(charPattern) {
20894
+ if (charPattern) {
20895
+ try {
20896
+ this.__allowedCharRegExp = new RegExp(`^${charPattern}$`);
20897
+ this.__allowedTextRegExp = new RegExp(`^${charPattern}*$`);
20898
+ } catch (e) {
20899
+ console.error(e);
20900
+ }
20901
+ }
20902
+ }
20903
+
20667
20904
  /**
20668
20905
  * Fired when the user commits a value change.
20669
20906
  *
@@ -20702,14 +20939,13 @@ class InputController extends SlotController {
20702
20939
  }
20703
20940
 
20704
20941
  // Ensure every instance has unique ID
20705
- const uniqueId = (InputController._uniqueInputId = 1 + InputController._uniqueInputId || 0);
20706
- host._inputId = `${host.localName}-${uniqueId}`;
20707
- node.id = host._inputId;
20942
+ node.id = this.defaultId;
20708
20943
 
20709
20944
  if (typeof callback === 'function') {
20710
20945
  callback(node);
20711
20946
  }
20712
20947
  },
20948
+ true,
20713
20949
  );
20714
20950
  }
20715
20951
  }
@@ -20891,7 +21127,9 @@ class VirtualKeyboardController {
20891
21127
  * @param {function(new:HTMLElement)} subclass
20892
21128
  */
20893
21129
  const DatePickerMixin = (subclass) =>
20894
- class VaadinDatePickerMixin extends ControllerMixin(DelegateFocusMixin(InputMixin(KeyboardMixin(subclass)))) {
21130
+ class VaadinDatePickerMixin extends ControllerMixin(
21131
+ DelegateFocusMixin(InputConstraintsMixin(KeyboardMixin(subclass))),
21132
+ ) {
20895
21133
  static get properties() {
20896
21134
  return {
20897
21135
  /**
@@ -20920,7 +21158,6 @@ const DatePickerMixin = (subclass) =>
20920
21158
  */
20921
21159
  value: {
20922
21160
  type: String,
20923
- observer: '_valueChanged',
20924
21161
  notify: true,
20925
21162
  value: '',
20926
21163
  },
@@ -20976,13 +21213,6 @@ const DatePickerMixin = (subclass) =>
20976
21213
  value: '(max-width: 420px), (max-height: 420px)',
20977
21214
  },
20978
21215
 
20979
- /**
20980
- * An array of ancestor elements whose -webkit-overflow-scrolling is forced from value
20981
- * 'touch' to value 'auto' in order to prevent them from clipping the dropdown. iOS only.
20982
- * @private
20983
- */
20984
- _touchPrevented: Array,
20985
-
20986
21216
  /**
20987
21217
  * The object used to localize this component.
20988
21218
  * To change the default localization, replace the entire
@@ -21138,7 +21368,6 @@ const DatePickerMixin = (subclass) =>
21138
21368
  */
21139
21369
  min: {
21140
21370
  type: String,
21141
- observer: '_minChanged',
21142
21371
  },
21143
21372
 
21144
21373
  /**
@@ -21152,28 +21381,26 @@ const DatePickerMixin = (subclass) =>
21152
21381
  */
21153
21382
  max: {
21154
21383
  type: String,
21155
- observer: '_maxChanged',
21156
21384
  },
21157
21385
 
21158
21386
  /**
21159
21387
  * The earliest date that can be selected. All earlier dates will be disabled.
21160
- * @type {Date | string}
21388
+ * @type {Date | undefined}
21161
21389
  * @protected
21162
21390
  */
21163
21391
  _minDate: {
21164
21392
  type: Date,
21165
- // Null does not work here because minimizer passes undefined to overlay (#351)
21166
- value: '',
21393
+ computed: '__computeMinOrMaxDate(min)',
21167
21394
  },
21168
21395
 
21169
21396
  /**
21170
21397
  * The latest date that can be selected. All later dates will be disabled.
21171
- * @type {Date | string}
21398
+ * @type {Date | undefined}
21172
21399
  * @protected
21173
21400
  */
21174
21401
  _maxDate: {
21175
21402
  type: Date,
21176
- value: '',
21403
+ computed: '__computeMinOrMaxDate(max)',
21177
21404
  },
21178
21405
 
21179
21406
  /** @private */
@@ -21188,12 +21415,6 @@ const DatePickerMixin = (subclass) =>
21188
21415
  value: isIOS,
21189
21416
  },
21190
21417
 
21191
- /** @private */
21192
- _webkitOverflowScroll: {
21193
- type: Boolean,
21194
- value: document.createElement('div').style.webkitOverflowScrolling === '',
21195
- },
21196
-
21197
21418
  /** @private */
21198
21419
  _focusOverlayOnOpen: Boolean,
21199
21420
 
@@ -21209,6 +21430,10 @@ const DatePickerMixin = (subclass) =>
21209
21430
  ];
21210
21431
  }
21211
21432
 
21433
+ static get constraints() {
21434
+ return [...super.constraints, 'min', 'max'];
21435
+ }
21436
+
21212
21437
  /**
21213
21438
  * Override a getter from `InputControlMixin` to make it optional
21214
21439
  * and to prevent warning when a clear button is missing,
@@ -21275,12 +21500,10 @@ const DatePickerMixin = (subclass) =>
21275
21500
  }
21276
21501
  }
21277
21502
 
21278
- if (this.inputElement.value === '' && this.__dispatchChange) {
21279
- this.validate();
21503
+ this.validate();
21504
+
21505
+ if (this._inputValue === '' && this.value !== '') {
21280
21506
  this.value = '';
21281
- this.__dispatchChange = false;
21282
- } else {
21283
- this.validate();
21284
21507
  }
21285
21508
  }
21286
21509
  }
@@ -21349,14 +21572,19 @@ const DatePickerMixin = (subclass) =>
21349
21572
  this.$.overlay.removeAttribute('disable-upgrade');
21350
21573
  this._overlayInitialized = true;
21351
21574
 
21352
- this.$.overlay.addEventListener('opened-changed', (e) => (this.opened = e.detail.value));
21575
+ this.$.overlay.addEventListener('opened-changed', (e) => {
21576
+ this.opened = e.detail.value;
21577
+ });
21353
21578
 
21354
21579
  this.$.overlay.addEventListener('vaadin-overlay-escape-press', () => {
21355
21580
  this._focusedDate = this._selectedDate;
21356
21581
  this._close();
21357
21582
  });
21358
21583
 
21359
- this._overlayContent.addEventListener('close', this._close.bind(this));
21584
+ this._overlayContent.addEventListener('close', () => {
21585
+ this._close();
21586
+ });
21587
+
21360
21588
  this._overlayContent.addEventListener('focus-input', this._focusAndSelect.bind(this));
21361
21589
 
21362
21590
  // User confirmed selected date by clicking the calendar.
@@ -21365,7 +21593,7 @@ const DatePickerMixin = (subclass) =>
21365
21593
 
21366
21594
  this._selectDate(e.detail.date);
21367
21595
 
21368
- this._close(e);
21596
+ this._close();
21369
21597
  });
21370
21598
 
21371
21599
  // User confirmed selected date by pressing Enter or Today.
@@ -21375,24 +21603,18 @@ const DatePickerMixin = (subclass) =>
21375
21603
  this._selectDate(e.detail.date);
21376
21604
  });
21377
21605
 
21378
- // Keep focus attribute in focusElement for styling
21606
+ // Set focus-ring attribute when moving focus to the overlay
21607
+ // by pressing Tab or arrow key, after opening it on click.
21379
21608
  this._overlayContent.addEventListener('focusin', () => {
21380
- this._setFocused(true);
21609
+ if (this._keyboardActive) {
21610
+ this._setFocused(true);
21611
+ }
21381
21612
  });
21382
21613
 
21383
21614
  this.addEventListener('mousedown', () => this.__bringToFront());
21384
21615
  this.addEventListener('touchstart', () => this.__bringToFront());
21385
21616
  }
21386
21617
 
21387
- /**
21388
- * Returns true if `value` is valid, and sets the `invalid` flag appropriately.
21389
- *
21390
- * @return {boolean} True if the value is valid and sets the `invalid` flag appropriately
21391
- */
21392
- validate() {
21393
- return !(this.invalid = !this.checkValidity());
21394
- }
21395
-
21396
21618
  /**
21397
21619
  * Returns true if the current input value satisfies all constraints (if any)
21398
21620
  *
@@ -21403,7 +21625,7 @@ const DatePickerMixin = (subclass) =>
21403
21625
  checkValidity() {
21404
21626
  const inputValid =
21405
21627
  !this._inputValue ||
21406
- (this._selectedDate && this._inputValue === this._getFormattedDate(this.i18n.formatDate, this._selectedDate));
21628
+ (!!this._selectedDate && this._inputValue === this._getFormattedDate(this.i18n.formatDate, this._selectedDate));
21407
21629
  const minMaxValid = !this._selectedDate || dateAllowed(this._selectedDate, this._minDate, this._maxDate);
21408
21630
 
21409
21631
  let inputValidity = true;
@@ -21419,6 +21641,51 @@ const DatePickerMixin = (subclass) =>
21419
21641
  return inputValid && minMaxValid && inputValidity;
21420
21642
  }
21421
21643
 
21644
+ /**
21645
+ * Override method inherited from `FocusMixin`
21646
+ * to not call `_setFocused(true)` when focus
21647
+ * is restored after closing overlay on click,
21648
+ * and to avoid removing `focus-ring` attribute.
21649
+ *
21650
+ * @param {!FocusEvent} _event
21651
+ * @return {boolean}
21652
+ * @protected
21653
+ * @override
21654
+ */
21655
+ _shouldSetFocus(_event) {
21656
+ return !this._shouldKeepFocusRing;
21657
+ }
21658
+
21659
+ /**
21660
+ * Override method inherited from `FocusMixin`
21661
+ * to prevent removing the `focused` attribute:
21662
+ * - when moving focus to the overlay content,
21663
+ * - when closing on date click / outside click.
21664
+ *
21665
+ * @param {!FocusEvent} _event
21666
+ * @return {boolean}
21667
+ * @protected
21668
+ * @override
21669
+ */
21670
+ _shouldRemoveFocus(_event) {
21671
+ return !this.opened;
21672
+ }
21673
+
21674
+ /**
21675
+ * Override method inherited from `FocusMixin`
21676
+ * to store the `focus-ring` state to restore
21677
+ * it later when closing on outside click.
21678
+ *
21679
+ * @param {boolean} focused
21680
+ * @protected
21681
+ * @override
21682
+ */
21683
+ _setFocused(focused) {
21684
+ super._setFocused(focused);
21685
+
21686
+ this._shouldKeepFocusRing = focused && this._keyboardActive;
21687
+ }
21688
+
21422
21689
  /**
21423
21690
  * Select date on user interaction and set the flag
21424
21691
  * to fire change event if necessary.
@@ -21438,10 +21705,7 @@ const DatePickerMixin = (subclass) =>
21438
21705
  }
21439
21706
 
21440
21707
  /** @private */
21441
- _close(e) {
21442
- if (e) {
21443
- e.stopPropagation();
21444
- }
21708
+ _close() {
21445
21709
  this._focus();
21446
21710
  this.close();
21447
21711
  }
@@ -21570,47 +21834,46 @@ const DatePickerMixin = (subclass) =>
21570
21834
  }
21571
21835
  }
21572
21836
 
21573
- /** @private */
21574
- _handleDateChange(property, value, oldValue) {
21575
- if (!value) {
21576
- this[property] = '';
21577
- return;
21578
- }
21837
+ /**
21838
+ * Override the value observer from `InputMixin` to implement custom
21839
+ * handling of the `value` property. The date-picker doesn't forward
21840
+ * the value directly to the input like the default implementation of `InputMixin`.
21841
+ * Instead, it parses the value into a date, puts it in `_selectedDate` which
21842
+ * is then displayed in the input with respect to the specified date format.
21843
+ *
21844
+ * @param {string | undefined} value
21845
+ * @param {string | undefined} oldValue
21846
+ * @protected
21847
+ * @override
21848
+ */
21849
+ _valueChanged(value, oldValue) {
21850
+ const newDate = this._parseDate(value);
21579
21851
 
21580
- const date = this._parseDate(value);
21581
- if (!date) {
21852
+ if (value && !newDate) {
21853
+ // The new value cannot be parsed, revert the old value.
21582
21854
  this.value = oldValue;
21583
21855
  return;
21584
21856
  }
21585
- if (!dateEquals(this[property], date)) {
21586
- this[property] = date;
21587
- if (this.value) {
21588
- this.validate();
21589
- }
21590
- }
21591
- }
21592
21857
 
21593
- /** @private */
21594
- _valueChanged(value, oldValue) {
21595
- this._handleDateChange('_selectedDate', value, oldValue);
21596
-
21597
- this._toggleHasValue(!!value);
21598
- }
21858
+ if (value) {
21859
+ if (!dateEquals(this._selectedDate, newDate)) {
21860
+ // Update the date instance only if the date has actually changed.
21861
+ this._selectedDate = newDate;
21599
21862
 
21600
- /** @private */
21601
- _minChanged(value, oldValue) {
21602
- this._handleDateChange('_minDate', value, oldValue);
21603
- }
21863
+ if (oldValue !== undefined) {
21864
+ // Validate only if `value` changes after initialization.
21865
+ this.validate();
21866
+ }
21867
+ }
21868
+ } else {
21869
+ this._selectedDate = null;
21870
+ }
21604
21871
 
21605
- /** @private */
21606
- _maxChanged(value, oldValue) {
21607
- this._handleDateChange('_maxDate', value, oldValue);
21872
+ this._toggleHasValue(this._hasValue);
21608
21873
  }
21609
21874
 
21610
21875
  /** @protected */
21611
21876
  _onOverlayOpened() {
21612
- this._openedWithFocusRing = this.hasAttribute('focus-ring');
21613
-
21614
21877
  const parsedInitialPosition = this._parseDate(this.initialPosition);
21615
21878
 
21616
21879
  const initialPosition =
@@ -21630,10 +21893,6 @@ const DatePickerMixin = (subclass) =>
21630
21893
 
21631
21894
  window.addEventListener('scroll', this._boundOnScroll, true);
21632
21895
 
21633
- if (this._webkitOverflowScroll) {
21634
- this._touchPrevented = this._preventWebkitOverflowScrollingTouch(this.parentElement);
21635
- }
21636
-
21637
21896
  if (this._focusOverlayOnOpen) {
21638
21897
  this._overlayContent.focusDateElement();
21639
21898
  this._focusOverlayOnOpen = false;
@@ -21647,25 +21906,6 @@ const DatePickerMixin = (subclass) =>
21647
21906
  }
21648
21907
  }
21649
21908
 
21650
- // A hack needed for iOS to prevent dropdown from being clipped in an
21651
- // ancestor container with -webkit-overflow-scrolling: touch;
21652
- /** @private */
21653
- _preventWebkitOverflowScrollingTouch(element) {
21654
- const result = [];
21655
- while (element) {
21656
- if (window.getComputedStyle(element).webkitOverflowScrolling === 'touch') {
21657
- const oldInlineValue = element.style.webkitOverflowScrolling;
21658
- element.style.webkitOverflowScrolling = 'auto';
21659
- result.push({
21660
- element,
21661
- oldInlineValue,
21662
- });
21663
- }
21664
- element = element.parentElement;
21665
- }
21666
- return result;
21667
- }
21668
-
21669
21909
  /** @private */
21670
21910
  _selectParsedOrFocusedDate() {
21671
21911
  // Select the parsed input or focused date
@@ -21692,13 +21932,6 @@ const DatePickerMixin = (subclass) =>
21692
21932
  _onOverlayClosed() {
21693
21933
  window.removeEventListener('scroll', this._boundOnScroll, true);
21694
21934
 
21695
- if (this._touchPrevented) {
21696
- this._touchPrevented.forEach(
21697
- (prevented) => (prevented.element.style.webkitOverflowScrolling = prevented.oldInlineValue),
21698
- );
21699
- this._touchPrevented = [];
21700
- }
21701
-
21702
21935
  // No need to select date on close if it was confirmed by the user.
21703
21936
  if (this.__userConfirmedDate) {
21704
21937
  this.__userConfirmedDate = false;
@@ -21714,11 +21947,6 @@ const DatePickerMixin = (subclass) =>
21714
21947
  if (!this.value) {
21715
21948
  this.validate();
21716
21949
  }
21717
-
21718
- // If the input isn't focused when overlay closes (fullscreen mode), clear focused state
21719
- if (this.getRootNode().activeElement !== this.inputElement) {
21720
- this._setFocused(false);
21721
- }
21722
21950
  }
21723
21951
 
21724
21952
  /** @private */
@@ -21771,10 +21999,7 @@ const DatePickerMixin = (subclass) =>
21771
21999
  _onChange(event) {
21772
22000
  // For change event on the native <input> blur, after the input is cleared,
21773
22001
  // we schedule change event to be dispatched on date-picker blur.
21774
- if (
21775
- this.inputElement.value === '' &&
21776
- !(event.detail && event.detail.sourceEvent && event.detail.sourceEvent.__fromClearButton)
21777
- ) {
22002
+ if (this._inputValue === '') {
21778
22003
  this.__dispatchChange = true;
21779
22004
  }
21780
22005
 
@@ -21861,7 +22086,7 @@ const DatePickerMixin = (subclass) =>
21861
22086
  if (e.shiftKey) {
21862
22087
  this._overlayContent.focusCancel();
21863
22088
  } else {
21864
- this._overlayContent.focusDate(this._focusedDate);
22089
+ this._overlayContent.focusDateElement();
21865
22090
  }
21866
22091
  }
21867
22092
  break;
@@ -21972,6 +22197,11 @@ const DatePickerMixin = (subclass) =>
21972
22197
  return this.$.overlay.content.querySelector('#overlay-content');
21973
22198
  }
21974
22199
 
22200
+ /** @private */
22201
+ __computeMinOrMaxDate(dateString) {
22202
+ return this._parseDate(dateString);
22203
+ }
22204
+
21975
22205
  /**
21976
22206
  * Fired when the user commits a value change.
21977
22207
  *
@@ -22083,12 +22313,13 @@ registerStyles('vaadin-date-picker', [inputFieldShared, datePickerStyles], { mod
22083
22313
  * Note: the `theme` attribute value set on `<vaadin-date-picker>` is
22084
22314
  * propagated to the internal components listed above.
22085
22315
  *
22086
- * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
22316
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
22087
22317
  *
22088
22318
  * @fires {Event} change - Fired when the user commits a value change.
22089
22319
  * @fires {CustomEvent} invalid-changed - Fired when the `invalid` property changes.
22090
22320
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
22091
22321
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
22322
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
22092
22323
  *
22093
22324
  * @extends HTMLElement
22094
22325
  * @mixes ElementMixin
@@ -22142,7 +22373,7 @@ class DatePicker extends DatePickerMixin(InputControlMixin(ThemableMixin(Element
22142
22373
  fullscreen$="[[_fullscreen]]"
22143
22374
  theme$="[[__getOverlayTheme(_theme, _overlayInitialized)]]"
22144
22375
  on-vaadin-overlay-open="_onOverlayOpened"
22145
- on-vaadin-overlay-close="_onOverlayClosed"
22376
+ on-vaadin-overlay-closing="_onOverlayClosed"
22146
22377
  restore-focus-on-close
22147
22378
  restore-focus-node="[[inputElement]]"
22148
22379
  disable-upgrade
@@ -22202,11 +22433,6 @@ class DatePicker extends DatePickerMixin(InputControlMixin(ThemableMixin(Element
22202
22433
 
22203
22434
  /** @private */
22204
22435
  _onVaadinOverlayClose(e) {
22205
- if (this._openedWithFocusRing && this.hasAttribute('focused')) {
22206
- this.setAttribute('focus-ring', '');
22207
- } else if (!this.hasAttribute('focused')) {
22208
- this.blur();
22209
- }
22210
22436
  if (e.detail.sourceEvent && e.detail.sourceEvent.composedPath().includes(this)) {
22211
22437
  e.preventDefault();
22212
22438
  }