@vaadin/date-picker 25.3.0-alpha2 → 25.3.0-dev.3a3c2d7d2a

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.
@@ -513,6 +513,16 @@
513
513
  "package": "@vaadin/a11y-base/src/tabindex-mixin.js"
514
514
  }
515
515
  },
516
+ {
517
+ "kind": "field",
518
+ "name": "disabledDatesProvider",
519
+ "privacy": "public",
520
+ "type": {
521
+ "text": "function(DatePickerDateRange): Array<DatePickerDate> | Promise<Array<DatePickerDate>> | undefined"
522
+ },
523
+ "description": "A batch function that is consulted for a range of dates that the calendar is about\nto render. It receives a `DatePickerDateRange` object (`{ start, end }` of\n`DatePickerDate`) and returns, or resolves with, an array of `DatePickerDate` objects\nthat should be disabled within that range.\n\nUnlike `isDateDisabled`, which is called once per date, this function is called for a\nrange of dates at a time, and again as the calendar renders further dates. The size of\nthe range is decided by the calendar and may span multiple months. It may return a\n`Promise`, in which case the affected dates render in a non-selectable pending state\nuntil it resolves.\n\nDates disabled by this function are combined with `min`, `max` and `isDateDisabled`:\na date is disabled if it is out of the min/max range, or `isDateDisabled` returns\n`true`, or it is included in this function's result.\n\nKeep a stable reference to the function. Assigning a new function resets the internal\ncache and re-fetches every visible range.",
524
+ "attribute": "disabled-dates-provider"
525
+ },
516
526
  {
517
527
  "kind": "field",
518
528
  "name": "i18n",
@@ -895,6 +905,16 @@
895
905
  },
896
906
  "attribute": "calendars"
897
907
  },
908
+ {
909
+ "kind": "field",
910
+ "name": "disabledDatesProvider",
911
+ "privacy": "public",
912
+ "type": {
913
+ "text": "function(DatePickerDateRange): Array<DatePickerDate> | Promise<Array<DatePickerDate>> | undefined"
914
+ },
915
+ "description": "A batch function that is consulted for a range of dates the calendar is about to render\nand returns, or resolves with, an array of `DatePickerDate` objects to disable.",
916
+ "attribute": "disabled-dates-provider"
917
+ },
898
918
  {
899
919
  "kind": "field",
900
920
  "name": "enteredDate",
@@ -982,6 +1002,16 @@
982
1002
  "description": "Input label",
983
1003
  "attribute": "label"
984
1004
  },
1005
+ {
1006
+ "kind": "field",
1007
+ "name": "loading",
1008
+ "privacy": "public",
1009
+ "type": {
1010
+ "text": "boolean"
1011
+ },
1012
+ "description": "Reflected while data is being loaded, so the overlay can show a loading spinner.\nCurrently set while the disabled dates provider is resolving.",
1013
+ "attribute": "loading"
1014
+ },
985
1015
  {
986
1016
  "kind": "field",
987
1017
  "name": "maxDate",
@@ -1125,6 +1155,14 @@
1125
1155
  "description": "Input label",
1126
1156
  "fieldName": "label"
1127
1157
  },
1158
+ {
1159
+ "name": "loading",
1160
+ "type": {
1161
+ "text": "boolean"
1162
+ },
1163
+ "description": "Reflected while data is being loaded, so the overlay can show a loading spinner.\nCurrently set while the disabled dates provider is resolving.",
1164
+ "fieldName": "loading"
1165
+ },
1128
1166
  {
1129
1167
  "name": "scroll-duration",
1130
1168
  "type": {
@@ -1683,7 +1721,7 @@
1683
1721
  "declarations": [
1684
1722
  {
1685
1723
  "kind": "class",
1686
- "description": "`<vaadin-date-picker>` is an input field that allows to enter a date by typing or by selecting from a calendar overlay.\n\n```html\n<vaadin-date-picker label=\"Birthday\"></vaadin-date-picker>\n```\n\n```js\ndatePicker.value = '2016-03-02';\n```\n\nWhen the selected `value` is changed, a `value-changed` event is triggered.\n\n### Styling\n\nThe following custom properties are available for styling:\n\nCustom property | Description | Default\n-------------------------------|----------------------------|---------\n`--vaadin-field-default-width` | Default width of the field | `12em`\n\nThe following shadow DOM parts are available for styling:\n\nPart name | Description\n---------------------|----------------\n`label` | The label element\n`input-field` | The element that wraps prefix, value and buttons\n`field-button` | Set on both clear and toggle buttons\n`clear-button` | The clear button\n`error-message` | The error message element\n`helper-text` | The helper text element wrapper\n`required-indicator` | The `required` state indicator element\n`toggle-button` | The toggle button\n`backdrop` | Backdrop of the overlay\n`overlay` | The overlay container\n`content` | The overlay content\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n---------------------|---------------------------------\n`disabled` | Set when the element is disabled\n`has-value` | Set when the element has a value\n`has-label` | Set when the element has a label\n`has-helper` | Set when the element has helper text or slot\n`has-error-message` | Set when the element has an error message\n`has-tooltip` | Set when the element has a slotted tooltip\n`invalid` | Set when the element is invalid\n`focused` | Set when the element is focused\n`focus-ring` | Set when the element is keyboard focused\n`readonly` | Set when the element is readonly\n`opened` | Set when the overlay is opened\n`week-numbers` | Set when week numbers are shown in the calendar\n\n### Internal components\n\nIn addition to `<vaadin-date-picker>` itself, the following internal\ncomponents are themable:\n\n- `<vaadin-date-picker-overlay-content>`\n- `<vaadin-date-picker-month-scroller>`\n- `<vaadin-date-picker-year-scroller>`\n- `<vaadin-date-picker-year>`\n- `<vaadin-month-calendar>`\n\nIn order to style the overlay content, use `<vaadin-date-picker-overlay-content>` shadow DOM parts:\n\nPart name | Description\n----------------------|--------------------\n`years-toggle-button` | Fullscreen mode years scroller toggle\n`toolbar` | Toolbar with slotted buttons\n\nThe following state attributes are available on the `<vaadin-date-picker-overlay-content>` element:\n\nAttribute | Description\n----------------|-------------------------------------------------\n`desktop` | Set when the overlay content is in desktop mode\n`fullscreen` | Set when the overlay content is in fullscreen mode\n`years-visible` | Set when the year scroller is visible in fullscreen mode\n\nIn order to style the month calendar, use `<vaadin-month-calendar>` shadow DOM parts:\n\nPart name | Description\n----------------------|--------------------\n`month-header` | Month title\n`weekdays` | Weekday container\n`weekday` | Weekday element\n`week-numbers` | Week numbers container\n`week-number` | Week number element\n`date` | Date element\n`disabled` | Disabled date element\n`focused` | Focused date element\n`selected` | Selected date element\n`today` | Date element corresponding to the current day\n`past` | Date element corresponding to the date in the past\n`future` | Date element corresponding to the date in the future\n\nIn order to style year scroller elements, use `<vaadin-date-picker-year>` shadow DOM parts:\n\nPart name | Description\n----------------------|--------------------\n`year-number` | Year number\n`year-separator` | Year separator\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.\n\n### Change events\n\nDepending on the nature of the value change that the user attempts to commit e.g. by pressing Enter,\nthe component can fire either a `change` event or an `unparsable-change` event:\n\nValue change | Event\n:------------------------|:------------------\nempty => parsable | change\nempty => unparsable | unparsable-change\nparsable => empty | change\nparsable => parsable | change\nparsable => unparsable | change\nunparsable => empty | unparsable-change\nunparsable => parsable | change\nunparsable => unparsable | unparsable-change",
1724
+ "description": "`<vaadin-date-picker>` is an input field that allows to enter a date by typing or by selecting from a calendar overlay.\n\n```html\n<vaadin-date-picker label=\"Birthday\"></vaadin-date-picker>\n```\n\n```js\ndatePicker.value = '2016-03-02';\n```\n\nWhen the selected `value` is changed, a `value-changed` event is triggered.\n\n### Styling\n\nThe following custom properties are available for styling:\n\nCustom property | Description | Default\n-------------------------------|----------------------------|---------\n`--vaadin-field-default-width` | Default width of the field | `12em`\n\nThe following shadow DOM parts are available for styling:\n\nPart name | Description\n---------------------|----------------\n`label` | The label element\n`input-field` | The element that wraps prefix, value and buttons\n`field-button` | Set on both clear and toggle buttons\n`clear-button` | The clear button\n`error-message` | The error message element\n`helper-text` | The helper text element wrapper\n`required-indicator` | The `required` state indicator element\n`toggle-button` | The toggle button\n`backdrop` | Backdrop of the overlay\n`overlay` | The overlay container\n`content` | The overlay content\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n---------------------|---------------------------------\n`disabled` | Set when the element is disabled\n`has-value` | Set when the element has a value\n`has-label` | Set when the element has a label\n`has-helper` | Set when the element has helper text or slot\n`has-error-message` | Set when the element has an error message\n`has-tooltip` | Set when the element has a slotted tooltip\n`invalid` | Set when the element is invalid\n`focused` | Set when the element is focused\n`focus-ring` | Set when the element is keyboard focused\n`readonly` | Set when the element is readonly\n`opened` | Set when the overlay is opened\n`week-numbers` | Set when week numbers are shown in the calendar\n\n### Internal components\n\nIn addition to `<vaadin-date-picker>` itself, the following internal\ncomponents are themable:\n\n- `<vaadin-date-picker-overlay-content>`\n- `<vaadin-date-picker-month-scroller>`\n- `<vaadin-date-picker-year-scroller>`\n- `<vaadin-date-picker-year>`\n- `<vaadin-month-calendar>`\n\nIn order to style the overlay content, use `<vaadin-date-picker-overlay-content>` shadow DOM parts:\n\nPart name | Description\n----------------------|--------------------\n`years-toggle-button` | Fullscreen mode years scroller toggle\n`toolbar` | Toolbar with slotted buttons\n`loader` | Loading spinner shown while data is being loaded, for example while the disabled dates provider resolves\n\nThe following state attributes are available on the `<vaadin-date-picker-overlay-content>` element:\n\nAttribute | Description\n----------------|-------------------------------------------------\n`desktop` | Set when the overlay content is in desktop mode\n`fullscreen` | Set when the overlay content is in fullscreen mode\n`years-visible` | Set when the year scroller is visible in fullscreen mode\n`loading` | Set while data is being loaded, for example while the disabled dates provider resolves\n\nIn order to style the month calendar, use `<vaadin-month-calendar>` shadow DOM parts:\n\nPart name | Description\n----------------------|--------------------\n`month-header` | Month title\n`weekdays` | Weekday container\n`weekday` | Weekday element\n`week-numbers` | Week numbers container\n`week-number` | Week number element\n`date` | Date element\n`disabled` | Disabled date element\n`pending` | Date element in a month whose disabled dates provider result is still loading\n`focused` | Focused date element\n`selected` | Selected date element\n`today` | Date element corresponding to the current day\n`past` | Date element corresponding to the date in the past\n`future` | Date element corresponding to the date in the future\n\nIn order to style year scroller elements, use `<vaadin-date-picker-year>` shadow DOM parts:\n\nPart name | Description\n----------------------|--------------------\n`year-number` | Year number\n`year-separator` | Year separator\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.\n\n### Change events\n\nDepending on the nature of the value change that the user attempts to commit e.g. by pressing Enter,\nthe component can fire either a `change` event or an `unparsable-change` event:\n\nValue change | Event\n:------------------------|:------------------\nempty => parsable | change\nempty => unparsable | unparsable-change\nparsable => empty | change\nparsable => parsable | change\nparsable => unparsable | change\nunparsable => empty | unparsable-change\nunparsable => parsable | change\nunparsable => unparsable | unparsable-change",
1687
1725
  "name": "DatePicker",
1688
1726
  "members": [
1689
1727
  {
@@ -1830,6 +1868,20 @@
1830
1868
  "package": "@vaadin/a11y-base/src/tabindex-mixin.js"
1831
1869
  }
1832
1870
  },
1871
+ {
1872
+ "kind": "field",
1873
+ "name": "disabledDatesProvider",
1874
+ "privacy": "public",
1875
+ "type": {
1876
+ "text": "function(DatePickerDateRange): Array<DatePickerDate> | Promise<Array<DatePickerDate>> | undefined"
1877
+ },
1878
+ "description": "A batch function that is consulted for a range of dates that the calendar is about\nto render. It receives a `DatePickerDateRange` object (`{ start, end }` of\n`DatePickerDate`) and returns, or resolves with, an array of `DatePickerDate` objects\nthat should be disabled within that range.\n\nUnlike `isDateDisabled`, which is called once per date, this function is called for a\nrange of dates at a time, and again as the calendar renders further dates. The size of\nthe range is decided by the calendar and may span multiple months. It may return a\n`Promise`, in which case the affected dates render in a non-selectable pending state\nuntil it resolves.\n\nDates disabled by this function are combined with `min`, `max` and `isDateDisabled`:\na date is disabled if it is out of the min/max range, or `isDateDisabled` returns\n`true`, or it is included in this function's result.\n\nKeep a stable reference to the function. Assigning a new function resets the internal\ncache and re-fetches every visible range.",
1879
+ "attribute": "disabled-dates-provider",
1880
+ "inheritedFrom": {
1881
+ "name": "DatePickerMixin",
1882
+ "module": "src/vaadin-date-picker-mixin.js"
1883
+ }
1884
+ },
1833
1885
  {
1834
1886
  "kind": "field",
1835
1887
  "name": "errorMessage",
@@ -2517,6 +2569,145 @@
2517
2569
  }
2518
2570
  ]
2519
2571
  },
2572
+ {
2573
+ "kind": "javascript-module",
2574
+ "path": "src/vaadin-disabled-dates-controller.js",
2575
+ "declarations": [
2576
+ {
2577
+ "kind": "class",
2578
+ "description": "A reactive controller that resolves the dates disabled by the date-picker's\n`disabledDatesProvider`. It calls the provider once for a range of months\n(never one date at a time), caches the resolved months so scrolling back and\nforth does not re-fetch them, and prefetches a buffer of months around the\nrequested range. While any request is in flight, #loading is `true`\nso the overlay can show a spinner.\n\nThe provider may return an array synchronously or a `Promise`, so results\nfrom a server (Flow) or a remote availability service can be awaited.",
2579
+ "name": "DisabledDatesController",
2580
+ "members": [
2581
+ {
2582
+ "kind": "method",
2583
+ "name": "ensureRangeLoaded",
2584
+ "parameters": [
2585
+ {
2586
+ "name": "startDate",
2587
+ "type": {
2588
+ "text": "Date"
2589
+ }
2590
+ },
2591
+ {
2592
+ "name": "endDate",
2593
+ "type": {
2594
+ "text": "Date"
2595
+ }
2596
+ }
2597
+ ],
2598
+ "description": "Ensures the provider has been consulted for the inclusive range between the\ngiven dates, expanded by a prefetch buffer of months on each side. Months\nthat are already loaded or in flight are skipped, and consecutive missing\nmonths are grouped into a single provider call."
2599
+ },
2600
+ {
2601
+ "kind": "field",
2602
+ "name": "host",
2603
+ "default": "host"
2604
+ },
2605
+ {
2606
+ "kind": "method",
2607
+ "name": "hostDisconnected"
2608
+ },
2609
+ {
2610
+ "kind": "method",
2611
+ "name": "isDateBlocked",
2612
+ "parameters": [
2613
+ {
2614
+ "name": "date",
2615
+ "type": {
2616
+ "text": "Date"
2617
+ }
2618
+ }
2619
+ ],
2620
+ "description": "Whether the given date cannot be selected yet: it is disabled by the\nprovider, or its month has not been resolved so its state is still unknown.\nReturns `false` when no provider is set. Used to block selection and to\nrender dates as non-selectable while their month is loading.",
2621
+ "return": {
2622
+ "type": {
2623
+ "text": "boolean"
2624
+ }
2625
+ }
2626
+ },
2627
+ {
2628
+ "kind": "method",
2629
+ "name": "isDateDisabled",
2630
+ "parameters": [
2631
+ {
2632
+ "name": "date",
2633
+ "type": {
2634
+ "text": "Date"
2635
+ }
2636
+ }
2637
+ ],
2638
+ "description": "Whether the given date is disabled by the provider. Only returns `true` for\ndates in an already-resolved month.",
2639
+ "return": {
2640
+ "type": {
2641
+ "text": "boolean"
2642
+ }
2643
+ }
2644
+ },
2645
+ {
2646
+ "kind": "method",
2647
+ "name": "isMonthLoaded",
2648
+ "parameters": [
2649
+ {
2650
+ "name": "date",
2651
+ "type": {
2652
+ "text": "Date"
2653
+ }
2654
+ }
2655
+ ],
2656
+ "description": "Whether the month containing the given date has been fully resolved.",
2657
+ "return": {
2658
+ "type": {
2659
+ "text": "boolean"
2660
+ }
2661
+ }
2662
+ },
2663
+ {
2664
+ "kind": "field",
2665
+ "name": "loading",
2666
+ "description": "Whether any month range is currently being fetched.",
2667
+ "return": {
2668
+ "type": {
2669
+ "text": "boolean"
2670
+ }
2671
+ },
2672
+ "readonly": true
2673
+ },
2674
+ {
2675
+ "kind": "field",
2676
+ "name": "provider",
2677
+ "type": {
2678
+ "text": "null"
2679
+ },
2680
+ "default": "null"
2681
+ },
2682
+ {
2683
+ "kind": "method",
2684
+ "name": "reset",
2685
+ "description": "Clears the cache and invalidates any in-flight requests."
2686
+ },
2687
+ {
2688
+ "kind": "method",
2689
+ "name": "setProvider",
2690
+ "parameters": [
2691
+ {
2692
+ "name": "provider"
2693
+ }
2694
+ ],
2695
+ "description": "Sets the provider function and clears the cache. Passing the same provider\nagain is a no-op, so the cache survives unrelated re-renders — callers\nshould therefore keep a stable provider reference rather than passing a new\nfunction on every update, which would otherwise reset the cache and\nre-fetch every visible range."
2696
+ }
2697
+ ]
2698
+ }
2699
+ ],
2700
+ "exports": [
2701
+ {
2702
+ "kind": "js",
2703
+ "name": "DisabledDatesController",
2704
+ "declaration": {
2705
+ "name": "DisabledDatesController",
2706
+ "module": "src/vaadin-disabled-dates-controller.js"
2707
+ }
2708
+ }
2709
+ ]
2710
+ },
2520
2711
  {
2521
2712
  "kind": "javascript-module",
2522
2713
  "path": "src/vaadin-month-calendar-mixin.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/date-picker",
3
- "version": "25.3.0-alpha2",
3
+ "version": "25.3.0-dev.3a3c2d7d2a",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -35,21 +35,21 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "@open-wc/dedupe-mixin": "^1.3.0",
38
- "@vaadin/a11y-base": "25.3.0-alpha2",
39
- "@vaadin/button": "25.3.0-alpha2",
40
- "@vaadin/component-base": "25.3.0-alpha2",
41
- "@vaadin/field-base": "25.3.0-alpha2",
42
- "@vaadin/input-container": "25.3.0-alpha2",
43
- "@vaadin/overlay": "25.3.0-alpha2",
44
- "@vaadin/vaadin-themable-mixin": "25.3.0-alpha2",
38
+ "@vaadin/a11y-base": "25.3.0-dev.3a3c2d7d2a",
39
+ "@vaadin/button": "25.3.0-dev.3a3c2d7d2a",
40
+ "@vaadin/component-base": "25.3.0-dev.3a3c2d7d2a",
41
+ "@vaadin/field-base": "25.3.0-dev.3a3c2d7d2a",
42
+ "@vaadin/input-container": "25.3.0-dev.3a3c2d7d2a",
43
+ "@vaadin/overlay": "25.3.0-dev.3a3c2d7d2a",
44
+ "@vaadin/vaadin-themable-mixin": "25.3.0-dev.3a3c2d7d2a",
45
45
  "lit": "^3.0.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@vaadin/aura": "25.3.0-alpha2",
49
- "@vaadin/chai-plugins": "25.3.0-alpha2",
50
- "@vaadin/test-runner-commands": "25.3.0-alpha2",
48
+ "@vaadin/aura": "25.3.0-dev.3a3c2d7d2a",
49
+ "@vaadin/chai-plugins": "25.3.0-dev.3a3c2d7d2a",
50
+ "@vaadin/test-runner-commands": "25.3.0-dev.3a3c2d7d2a",
51
51
  "@vaadin/testing-helpers": "^2.0.0",
52
- "@vaadin/vaadin-lumo-styles": "25.3.0-alpha2",
52
+ "@vaadin/vaadin-lumo-styles": "25.3.0-dev.3a3c2d7d2a",
53
53
  "sinon": "^22.0.0"
54
54
  },
55
55
  "customElements": "custom-elements.json",
@@ -57,5 +57,5 @@
57
57
  "web-types.json",
58
58
  "web-types.lit.json"
59
59
  ],
60
- "gitHead": "52eba6153ecf8bfaae44bcb727595e77d90b963c"
60
+ "gitHead": "ac506ca7800c23e05da55863c56bcedf12812d37"
61
61
  }
@@ -17,6 +17,7 @@ export const overlayContentStyles = css`
17
17
  height: 100%;
18
18
  outline: none;
19
19
  overflow: hidden;
20
+ position: relative;
20
21
  }
21
22
 
22
23
  :host([desktop]) {
@@ -49,6 +50,17 @@ export const overlayContentStyles = css`
49
50
  display: none !important;
50
51
  }
51
52
 
53
+ /* Loading spinner shown while data is being loaded (currently while the disabled dates provider
54
+ resolves). Positioned over the month scroller, which paints its own transformed layers, so
55
+ grid stacking is not enough. */
56
+ [part='loader'] {
57
+ position: absolute;
58
+ z-index: 1;
59
+ inset-block-start: var(--vaadin-date-picker-month-header-font-size, 0.9375rem);
60
+ inset-inline: 0;
61
+ margin-inline: auto;
62
+ }
63
+
52
64
  ::slotted([slot='months']) {
53
65
  --vaadin-infinite-scroller-item-height: round(
54
66
  var(--vaadin-date-picker-month-header-font-size, 0.9375rem) + 0.75rem +
@@ -18,6 +18,17 @@ export interface DatePickerDate {
18
18
  year: number;
19
19
  }
20
20
 
21
+ export interface DatePickerDateRange {
22
+ /**
23
+ * The first date of the range (inclusive).
24
+ */
25
+ start: DatePickerDate;
26
+ /**
27
+ * The last date of the range (inclusive).
28
+ */
29
+ end: DatePickerDate;
30
+ }
31
+
21
32
  export interface DatePickerI18n {
22
33
  /**
23
34
  * An array with the full names of months starting
@@ -236,6 +247,23 @@ export declare class DatePickerMixinClass {
236
247
  */
237
248
  isDateDisabled: (date: DatePickerDate) => boolean;
238
249
 
250
+ /**
251
+ * A batch function that is consulted for a range of dates that the calendar is about to
252
+ * render. It receives a `DatePickerDateRange` object and returns, or resolves with, an array
253
+ * of `DatePickerDate` objects that should be disabled within that range.
254
+ *
255
+ * Unlike `isDateDisabled`, which is called once per date, this function is called for a range
256
+ * of dates at a time, and again as the calendar renders further dates. The size of the range
257
+ * is decided by the calendar and may span multiple months. It may return a `Promise`, in which
258
+ * case the affected dates render in a non-selectable pending state until it resolves.
259
+ *
260
+ * Dates disabled by this function are combined with `min`, `max` and `isDateDisabled`.
261
+ *
262
+ * Keep a stable reference to the function. Assigning a new function resets the internal cache
263
+ * and re-fetches every visible range.
264
+ */
265
+ disabledDatesProvider: (range: DatePickerDateRange) => DatePickerDate[] | Promise<DatePickerDate[]>;
266
+
239
267
  /**
240
268
  * Opens the dropdown.
241
269
  */
@@ -21,6 +21,7 @@ import {
21
21
  getClosestDate,
22
22
  parseDate,
23
23
  } from './vaadin-date-picker-helper.js';
24
+ import { DisabledDatesController } from './vaadin-disabled-dates-controller.js';
24
25
 
25
26
  export const datePickerI18nDefaults = Object.freeze({
26
27
  monthNames: [
@@ -208,6 +209,31 @@ export const DatePickerMixin = (subclass) =>
208
209
  type: Function,
209
210
  },
210
211
 
212
+ /**
213
+ * A batch function that is consulted for a range of dates that the calendar is about
214
+ * to render. It receives a `DatePickerDateRange` object (`{ start, end }` of
215
+ * `DatePickerDate`) and returns, or resolves with, an array of `DatePickerDate` objects
216
+ * that should be disabled within that range.
217
+ *
218
+ * Unlike `isDateDisabled`, which is called once per date, this function is called for a
219
+ * range of dates at a time, and again as the calendar renders further dates. The size of
220
+ * the range is decided by the calendar and may span multiple months. It may return a
221
+ * `Promise`, in which case the affected dates render in a non-selectable pending state
222
+ * until it resolves.
223
+ *
224
+ * Dates disabled by this function are combined with `min`, `max` and `isDateDisabled`:
225
+ * a date is disabled if it is out of the min/max range, or `isDateDisabled` returns
226
+ * `true`, or it is included in this function's result.
227
+ *
228
+ * Keep a stable reference to the function. Assigning a new function resets the internal
229
+ * cache and re-fetches every visible range.
230
+ *
231
+ * @type {function(DatePickerDateRange): Array<DatePickerDate> | Promise<Array<DatePickerDate>> | undefined}
232
+ */
233
+ disabledDatesProvider: {
234
+ type: Function,
235
+ },
236
+
211
237
  /**
212
238
  * The earliest date that can be selected. All earlier dates will be disabled.
213
239
  * @type {Date | undefined}
@@ -261,7 +287,8 @@ export const DatePickerMixin = (subclass) =>
261
287
  return [
262
288
  '_selectedDateChanged(_selectedDate, __effectiveI18n)',
263
289
  '_focusedDateChanged(_focusedDate, __effectiveI18n)',
264
- '__updateOverlayContent(_overlayContent, __effectiveI18n, label, _minDate, _maxDate, _focusedDate, _selectedDate, showWeekNumbers, isDateDisabled, __enteredDate)',
290
+ '__updateOverlayContent(_overlayContent, __effectiveI18n, label, _minDate, _maxDate, _focusedDate, _selectedDate, showWeekNumbers, isDateDisabled, disabledDatesProvider, __enteredDate)',
291
+ '__disabledDatesProviderChanged(disabledDatesProvider)',
265
292
  '__updateOverlayContentTheme(_overlayContent, _theme)',
266
293
  '__updateOverlayContentFullScreen(_overlayContent, _fullscreen)',
267
294
  ];
@@ -437,6 +464,13 @@ export const DatePickerMixin = (subclass) =>
437
464
 
438
465
  this.addController(new VirtualKeyboardController(this));
439
466
 
467
+ // Owns the cache of dates resolved by `disabledDatesProvider`. It lives on the date-picker
468
+ // rather than the overlay content so validation works even when the overlay is never opened.
469
+ // The open overlay reads the same controller to render months and show the loading spinner.
470
+ this._disabledDatesController = new DisabledDatesController(this, () => this.__onDisabledDatesChanged());
471
+ this.addController(this._disabledDatesController);
472
+ this._disabledDatesController.setProvider(this.disabledDatesProvider);
473
+
440
474
  this._overlayElement = this.$.overlay;
441
475
  }
442
476
 
@@ -578,7 +612,9 @@ export const DatePickerMixin = (subclass) =>
578
612
  const inputValue = this._inputElementValue;
579
613
  const inputValid = !inputValue || (!!this._selectedDate && inputValue === this.__formatDate(this._selectedDate));
580
614
  const isDateValid =
581
- !this._selectedDate || dateAllowed(this._selectedDate, this._minDate, this._maxDate, this.isDateDisabled);
615
+ !this._selectedDate ||
616
+ (dateAllowed(this._selectedDate, this._minDate, this._maxDate, this.isDateDisabled) &&
617
+ !this.__isDateDisabledByProvider(this._selectedDate));
582
618
 
583
619
  let inputValidity = true;
584
620
  if (this.inputElement && this.inputElement.checkValidity) {
@@ -588,6 +624,93 @@ export const DatePickerMixin = (subclass) =>
588
624
  return inputValid && isDateValid && inputValidity;
589
625
  }
590
626
 
627
+ /**
628
+ * Returns true if the given date is known to be disabled by `disabledDatesProvider`. The
629
+ * result comes from the controller's cache of already-loaded ranges. The month containing a
630
+ * selected date is loaded on demand (see `_selectedDateChanged`), so a value typed while the
631
+ * overlay is closed is re-validated once the provider answers (see `__onDisabledDatesChanged`).
632
+ * Until then the date is treated as allowed, matching the overlay's rendering.
633
+ * @private
634
+ */
635
+ __isDateDisabledByProvider(date) {
636
+ const controller = this._disabledDatesController;
637
+ return !!controller && !!controller.provider && controller.isDateDisabled(date);
638
+ }
639
+
640
+ /** @private */
641
+ __disabledDatesProviderChanged(disabledDatesProvider) {
642
+ // The controller is created in `ready()`; `setProvider` is called there for the initial value.
643
+ if (this._disabledDatesController) {
644
+ this._disabledDatesController.setProvider(disabledDatesProvider);
645
+ this.__ensureSelectedDateLoaded();
646
+ }
647
+ }
648
+
649
+ /**
650
+ * Asks the controller to resolve the month containing the selected date, so that a value set
651
+ * or typed while the overlay is closed can be validated against the provider without opening
652
+ * the overlay. When the month resolves, `__onDisabledDatesChanged` re-runs validation.
653
+ * @private
654
+ */
655
+ __ensureSelectedDateLoaded() {
656
+ const controller = this._disabledDatesController;
657
+ if (controller?.provider && this._selectedDate && !controller.isMonthLoaded(this._selectedDate)) {
658
+ this.__awaitingProviderValidation = true;
659
+ controller.ensureRangeLoaded(this._selectedDate, this._selectedDate);
660
+ }
661
+ }
662
+
663
+ /** @private */
664
+ __onDisabledDatesChanged() {
665
+ const controller = this._disabledDatesController;
666
+ // Push the new loading and cache state to the open overlay so it re-renders the months and
667
+ // updates the spinner. When the overlay is closed there is nothing to update.
668
+ if (this._overlayContent) {
669
+ this._overlayContent.loading = controller.loading;
670
+ this._overlayContent._disabledDatesVersion += 1;
671
+ }
672
+ // Re-validate once the month containing the selected value has resolved, so a value typed
673
+ // while the picker was closed (autoOpenDisabled) is rejected as soon as the provider answers.
674
+ if (this.__awaitingProviderValidation && this._selectedDate && controller.isMonthLoaded(this._selectedDate)) {
675
+ this.__awaitingProviderValidation = false;
676
+ this._requestValidation();
677
+ }
678
+
679
+ this.__adjustInitialFocusForProvider();
680
+ }
681
+
682
+ /**
683
+ * Moves the overlay's initial focus off a date the provider turns out to disable, once the
684
+ * provider has answered for that month. Only touches the auto-picked initial date and only
685
+ * while the user has not navigated away, so disabled dates the user focuses on purpose (which
686
+ * stay keyboard-focusable) are left alone.
687
+ * @private
688
+ */
689
+ __adjustInitialFocusForProvider() {
690
+ const content = this._overlayContent;
691
+ const controller = this._disabledDatesController;
692
+ const initial = this.__initialFocusDate;
693
+ if (!content || !initial || !controller.provider) {
694
+ return;
695
+ }
696
+ // The user has moved focus; stop trying to adjust the initial date.
697
+ if (!dateEquals(content.focusedDate, initial)) {
698
+ this.__initialFocusDate = null;
699
+ return;
700
+ }
701
+ // Wait until the provider has answered for the initial month.
702
+ if (!controller.isMonthLoaded(initial)) {
703
+ return;
704
+ }
705
+ this.__initialFocusDate = null;
706
+ if (controller.isDateDisabled(initial)) {
707
+ const closest = content.__closestSelectableDate(initial);
708
+ if (closest) {
709
+ content.focusDate(closest);
710
+ }
711
+ }
712
+ }
713
+
591
714
  /**
592
715
  * Override method inherited from `FocusMixin`
593
716
  * to not call `_setFocused(true)` when focus
@@ -771,6 +894,10 @@ export const DatePickerMixin = (subclass) =>
771
894
  this._applyInputValue(selectedDate);
772
895
  }
773
896
 
897
+ // Preload the provider's answer for the selected month so the value can be validated even
898
+ // when the overlay is never opened.
899
+ this.__ensureSelectedDateLoaded();
900
+
774
901
  this.value = this._formatISO(selectedDate);
775
902
  this._ignoreFocusedDateChange = true;
776
903
  this._focusedDate = selectedDate;
@@ -842,9 +969,13 @@ export const DatePickerMixin = (subclass) =>
842
969
  selectedDate,
843
970
  showWeekNumbers,
844
971
  isDateDisabled,
972
+ disabledDatesProvider,
845
973
  enteredDate,
846
974
  ) {
847
975
  if (overlayContent) {
976
+ // Share the date-picker's controller so the overlay renders from the same cache that
977
+ // validation uses. Assigned before the other properties, which trigger `__updateCalendars`.
978
+ overlayContent._disabledDatesController = this._disabledDatesController;
848
979
  overlayContent.i18n = effectiveI18n;
849
980
  overlayContent.label = label;
850
981
  overlayContent.minDate = minDate;
@@ -853,6 +984,7 @@ export const DatePickerMixin = (subclass) =>
853
984
  overlayContent.selectedDate = selectedDate;
854
985
  overlayContent.showWeekNumbers = showWeekNumbers;
855
986
  overlayContent.isDateDisabled = isDateDisabled;
987
+ overlayContent.disabledDatesProvider = disabledDatesProvider;
856
988
  overlayContent.enteredDate = enteredDate;
857
989
  }
858
990
  }
@@ -901,6 +1033,12 @@ export const DatePickerMixin = (subclass) =>
901
1033
  content.focusedDate = scrollFocusDate;
902
1034
  this._ignoreFocusedDateChange = false;
903
1035
 
1036
+ // When opening without a selected value, remember the auto-picked initial date so it can be
1037
+ // moved off a provider-disabled date once the provider answers (see __onDisabledDatesChanged).
1038
+ // A date the user selected themselves is left in place even if the provider disables it.
1039
+ this.__initialFocusDate = this._selectedDate ? null : scrollFocusDate;
1040
+ this.__adjustInitialFocusForProvider();
1041
+
904
1042
  window.addEventListener('scroll', this._boundOnScroll, true);
905
1043
 
906
1044
  if (this._focusOverlayOnOpen) {
@@ -964,6 +1102,8 @@ export const DatePickerMixin = (subclass) =>
964
1102
 
965
1103
  /** @protected */
966
1104
  _onOverlayClosed() {
1105
+ this.__initialFocusDate = null;
1106
+
967
1107
  // Reset `aria-hidden` state.
968
1108
  if (this.__showOthers) {
969
1109
  this.__showOthers();