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