@everymatrix/helper-filters 0.1.0 → 0.1.4

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