tyrell-components 1.0.0-RC13 → 1.0.0-RC14
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.
- package/README.md +1 -1
- package/css/tyrell-theme.css +167 -178
- package/css/tyrell.css +19 -0
- package/dist/tyrell-theme.css +167 -178
- package/dist/tyrell.css +19 -0
- package/dist/tyrell.js +1 -1
- package/lib/base/ty-component.d.ts +20 -10
- package/lib/base/ty-component.js +21 -144
- package/lib/components/button.d.ts +2 -16
- package/lib/components/button.js +0 -36
- package/lib/components/calendar-month.d.ts +2 -48
- package/lib/components/calendar-month.js +4 -166
- package/lib/components/calendar-navigation.d.ts +3 -48
- package/lib/components/calendar-navigation.js +3 -126
- package/lib/components/calendar.d.ts +3 -80
- package/lib/components/calendar.js +8 -230
- package/lib/components/checkbox.d.ts +4 -71
- package/lib/components/checkbox.js +7 -151
- package/lib/components/copy.d.ts +3 -56
- package/lib/components/copy.js +1 -120
- package/lib/components/date-picker.d.ts +13 -162
- package/lib/components/date-picker.js +41 -592
- package/lib/components/file-upload.d.ts +9 -15
- package/lib/components/file-upload.js +19 -41
- package/lib/components/icon.d.ts +3 -24
- package/lib/components/icon.js +1 -90
- package/lib/components/input.d.ts +7 -107
- package/lib/components/input.js +5 -193
- package/lib/components/modal.d.ts +17 -26
- package/lib/components/modal.js +57 -201
- package/lib/components/option.d.ts +2 -9
- package/lib/components/option.js +0 -48
- package/lib/components/popup.d.ts +2 -17
- package/lib/components/popup.js +8 -139
- package/lib/components/radio.d.ts +3 -23
- package/lib/components/radio.js +10 -65
- package/lib/components/resize-observer.d.ts +3 -30
- package/lib/components/resize-observer.js +12 -46
- package/lib/components/scroll-container.d.ts +2 -12
- package/lib/components/scroll-container.js +3 -46
- package/lib/components/select.d.ts +22 -153
- package/lib/components/select.js +33 -590
- package/lib/components/selected-tags.d.ts +4 -47
- package/lib/components/selected-tags.js +1 -95
- package/lib/components/step.d.ts +1 -6
- package/lib/components/step.js +0 -6
- package/lib/components/switch.d.ts +0 -11
- package/lib/components/switch.js +2 -36
- package/lib/components/tab.d.ts +1 -6
- package/lib/components/tab.js +0 -7
- package/lib/components/tabs.d.ts +5 -63
- package/lib/components/tabs.js +20 -283
- package/lib/components/tag.d.ts +2 -36
- package/lib/components/tag.js +5 -105
- package/lib/components/textarea.d.ts +6 -86
- package/lib/components/textarea.js +2 -173
- package/lib/components/tooltip.d.ts +3 -11
- package/lib/components/tooltip.js +24 -160
- package/lib/components/wizard.d.ts +6 -54
- package/lib/components/wizard.js +15 -228
- package/lib/icons/fontawesome/brands.d.ts +0 -1
- package/lib/icons/fontawesome/brands.js +0 -8
- package/lib/icons/fontawesome/regular.d.ts +0 -1
- package/lib/icons/fontawesome/regular.js +0 -8
- package/lib/icons/fontawesome/solid.d.ts +0 -1
- package/lib/icons/fontawesome/solid.js +0 -8
- package/lib/icons/heroicons/micro.d.ts +0 -1
- package/lib/icons/heroicons/micro.js +0 -8
- package/lib/icons/heroicons/mini.d.ts +0 -1
- package/lib/icons/heroicons/mini.js +0 -8
- package/lib/icons/heroicons/outline.d.ts +0 -1
- package/lib/icons/heroicons/outline.js +0 -8
- package/lib/icons/heroicons/solid.d.ts +0 -1
- package/lib/icons/heroicons/solid.js +0 -8
- package/lib/icons/lucide.d.ts +0 -1
- package/lib/icons/lucide.js +0 -1872
- package/lib/icons/material/filled.d.ts +0 -1
- package/lib/icons/material/filled.js +0 -10
- package/lib/icons/material/outlined.d.ts +0 -1
- package/lib/icons/material/outlined.js +0 -10
- package/lib/icons/material/round.d.ts +0 -1
- package/lib/icons/material/round.js +0 -10
- package/lib/icons/material/sharp.d.ts +0 -1
- package/lib/icons/material/sharp.js +0 -10
- package/lib/icons/material/two-tone.d.ts +0 -1
- package/lib/icons/material/two-tone.js +0 -10
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -20
- package/lib/styles/button.d.ts +2 -20
- package/lib/styles/button.js +22 -122
- package/lib/styles/calendar-month.d.ts +1 -5
- package/lib/styles/calendar-month.js +4 -61
- package/lib/styles/calendar-navigation.d.ts +2 -6
- package/lib/styles/calendar-navigation.js +2 -27
- package/lib/styles/calendar.d.ts +1 -2
- package/lib/styles/calendar.js +1 -14
- package/lib/styles/checkbox.d.ts +0 -1
- package/lib/styles/checkbox.js +1 -23
- package/lib/styles/copy.d.ts +0 -1
- package/lib/styles/copy.js +2 -17
- package/lib/styles/custom-scrollbar.d.ts +1 -2
- package/lib/styles/custom-scrollbar.js +3 -18
- package/lib/styles/date-picker.d.ts +1 -5
- package/lib/styles/date-picker.js +35 -39
- package/lib/styles/file-upload.d.ts +1 -2
- package/lib/styles/file-upload.js +1 -16
- package/lib/styles/icon.d.ts +2 -6
- package/lib/styles/icon.js +7 -21
- package/lib/styles/input.d.ts +1 -6
- package/lib/styles/input.js +5 -54
- package/lib/styles/modal.d.ts +1 -2
- package/lib/styles/modal.js +21 -23
- package/lib/styles/option.d.ts +2 -6
- package/lib/styles/option.js +17 -12
- package/lib/styles/popup.d.ts +1 -2
- package/lib/styles/popup.js +26 -30
- package/lib/styles/radio.d.ts +0 -1
- package/lib/styles/radio.js +2 -16
- package/lib/styles/resize-observer.d.ts +1 -2
- package/lib/styles/resize-observer.js +0 -7
- package/lib/styles/scroll-container.d.ts +1 -2
- package/lib/styles/scroll-container.js +0 -19
- package/lib/styles/select-base.d.ts +0 -1
- package/lib/styles/select-base.js +68 -79
- package/lib/styles/select.d.ts +1 -2
- package/lib/styles/select.js +7 -15
- package/lib/styles/step.d.ts +1 -2
- package/lib/styles/step.js +0 -4
- package/lib/styles/switch.d.ts +0 -1
- package/lib/styles/switch.js +2 -20
- package/lib/styles/tab.d.ts +1 -2
- package/lib/styles/tab.js +0 -5
- package/lib/styles/tabs.d.ts +3 -11
- package/lib/styles/tabs.js +10 -48
- package/lib/styles/tag.d.ts +1 -5
- package/lib/styles/tag.js +2 -31
- package/lib/styles/textarea.d.ts +1 -2
- package/lib/styles/textarea.js +2 -45
- package/lib/styles/tooltip.d.ts +0 -1
- package/lib/styles/tooltip.js +0 -10
- package/lib/styles/wizard.d.ts +5 -21
- package/lib/styles/wizard.js +8 -88
- package/lib/types/common.d.ts +0 -2
- package/lib/types/common.js +0 -12
- package/lib/utils/calendar-utils.d.ts +4 -21
- package/lib/utils/calendar-utils.js +4 -177
- package/lib/utils/custom-scrollbar.d.ts +6 -20
- package/lib/utils/custom-scrollbar.js +1 -45
- package/lib/utils/flavor-sheet.d.ts +0 -1
- package/lib/utils/flavor-sheet.js +0 -23
- package/lib/utils/icon-registry.d.ts +0 -2
- package/lib/utils/icon-registry.js +1 -107
- package/lib/utils/icons.d.ts +0 -1
- package/lib/utils/icons.js +0 -9
- package/lib/utils/loader-registry.d.ts +0 -1
- package/lib/utils/loader-registry.js +0 -32
- package/lib/utils/locale.d.ts +2 -23
- package/lib/utils/locale.js +0 -86
- package/lib/utils/mobile.d.ts +0 -1
- package/lib/utils/mobile.js +0 -17
- package/lib/utils/number-format.d.ts +6 -3
- package/lib/utils/number-format.js +11 -67
- package/lib/utils/positioning.d.ts +50 -7
- package/lib/utils/positioning.js +52 -62
- package/lib/utils/property-capture.d.ts +5 -27
- package/lib/utils/property-capture.js +0 -108
- package/lib/utils/property-manager.d.ts +0 -1
- package/lib/utils/property-manager.js +0 -70
- package/lib/utils/resize-observer.d.ts +0 -1
- package/lib/utils/resize-observer.js +0 -36
- package/lib/utils/scroll-lock.d.ts +4 -11
- package/lib/utils/scroll-lock.js +0 -107
- package/lib/utils/styles.d.ts +0 -1
- package/lib/utils/styles.js +0 -24
- package/lib/version.d.ts +1 -2
- package/lib/version.js +1 -11
- package/package.json +2 -2
- package/lib/base/ty-component.d.ts.map +0 -1
- package/lib/base/ty-component.js.map +0 -1
- package/lib/components/button.d.ts.map +0 -1
- package/lib/components/button.js.map +0 -1
- package/lib/components/calendar-month.d.ts.map +0 -1
- package/lib/components/calendar-month.js.map +0 -1
- package/lib/components/calendar-navigation.d.ts.map +0 -1
- package/lib/components/calendar-navigation.js.map +0 -1
- package/lib/components/calendar.d.ts.map +0 -1
- package/lib/components/calendar.js.map +0 -1
- package/lib/components/checkbox.d.ts.map +0 -1
- package/lib/components/checkbox.js.map +0 -1
- package/lib/components/copy.d.ts.map +0 -1
- package/lib/components/copy.js.map +0 -1
- package/lib/components/date-picker.d.ts.map +0 -1
- package/lib/components/date-picker.js.map +0 -1
- package/lib/components/file-upload.d.ts.map +0 -1
- package/lib/components/file-upload.js.map +0 -1
- package/lib/components/icon.d.ts.map +0 -1
- package/lib/components/icon.js.map +0 -1
- package/lib/components/input.d.ts.map +0 -1
- package/lib/components/input.js.map +0 -1
- package/lib/components/modal.d.ts.map +0 -1
- package/lib/components/modal.js.map +0 -1
- package/lib/components/option.d.ts.map +0 -1
- package/lib/components/option.js.map +0 -1
- package/lib/components/popup.d.ts.map +0 -1
- package/lib/components/popup.js.map +0 -1
- package/lib/components/radio.d.ts.map +0 -1
- package/lib/components/radio.js.map +0 -1
- package/lib/components/resize-observer.d.ts.map +0 -1
- package/lib/components/resize-observer.js.map +0 -1
- package/lib/components/scroll-container.d.ts.map +0 -1
- package/lib/components/scroll-container.js.map +0 -1
- package/lib/components/select.d.ts.map +0 -1
- package/lib/components/select.js.map +0 -1
- package/lib/components/selected-tags.d.ts.map +0 -1
- package/lib/components/selected-tags.js.map +0 -1
- package/lib/components/step.d.ts.map +0 -1
- package/lib/components/step.js.map +0 -1
- package/lib/components/switch.d.ts.map +0 -1
- package/lib/components/switch.js.map +0 -1
- package/lib/components/tab.d.ts.map +0 -1
- package/lib/components/tab.js.map +0 -1
- package/lib/components/tabs.d.ts.map +0 -1
- package/lib/components/tabs.js.map +0 -1
- package/lib/components/tag.d.ts.map +0 -1
- package/lib/components/tag.js.map +0 -1
- package/lib/components/textarea.d.ts.map +0 -1
- package/lib/components/textarea.js.map +0 -1
- package/lib/components/tooltip.d.ts.map +0 -1
- package/lib/components/tooltip.js.map +0 -1
- package/lib/components/wizard.d.ts.map +0 -1
- package/lib/components/wizard.js.map +0 -1
- package/lib/icons/fontawesome/brands.d.ts.map +0 -1
- package/lib/icons/fontawesome/brands.js.map +0 -1
- package/lib/icons/fontawesome/regular.d.ts.map +0 -1
- package/lib/icons/fontawesome/regular.js.map +0 -1
- package/lib/icons/fontawesome/solid.d.ts.map +0 -1
- package/lib/icons/fontawesome/solid.js.map +0 -1
- package/lib/icons/heroicons/micro.d.ts.map +0 -1
- package/lib/icons/heroicons/micro.js.map +0 -1
- package/lib/icons/heroicons/mini.d.ts.map +0 -1
- package/lib/icons/heroicons/mini.js.map +0 -1
- package/lib/icons/heroicons/outline.d.ts.map +0 -1
- package/lib/icons/heroicons/outline.js.map +0 -1
- package/lib/icons/heroicons/solid.d.ts.map +0 -1
- package/lib/icons/heroicons/solid.js.map +0 -1
- package/lib/icons/lucide.d.ts.map +0 -1
- package/lib/icons/lucide.js.map +0 -1
- package/lib/icons/material/filled.d.ts.map +0 -1
- package/lib/icons/material/filled.js.map +0 -1
- package/lib/icons/material/outlined.d.ts.map +0 -1
- package/lib/icons/material/outlined.js.map +0 -1
- package/lib/icons/material/round.d.ts.map +0 -1
- package/lib/icons/material/round.js.map +0 -1
- package/lib/icons/material/sharp.d.ts.map +0 -1
- package/lib/icons/material/sharp.js.map +0 -1
- package/lib/icons/material/two-tone.d.ts.map +0 -1
- package/lib/icons/material/two-tone.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/styles/button.d.ts.map +0 -1
- package/lib/styles/button.js.map +0 -1
- package/lib/styles/calendar-month.d.ts.map +0 -1
- package/lib/styles/calendar-month.js.map +0 -1
- package/lib/styles/calendar-navigation.d.ts.map +0 -1
- package/lib/styles/calendar-navigation.js.map +0 -1
- package/lib/styles/calendar.d.ts.map +0 -1
- package/lib/styles/calendar.js.map +0 -1
- package/lib/styles/checkbox.d.ts.map +0 -1
- package/lib/styles/checkbox.js.map +0 -1
- package/lib/styles/copy.d.ts.map +0 -1
- package/lib/styles/copy.js.map +0 -1
- package/lib/styles/custom-scrollbar.d.ts.map +0 -1
- package/lib/styles/custom-scrollbar.js.map +0 -1
- package/lib/styles/date-picker.d.ts.map +0 -1
- package/lib/styles/date-picker.js.map +0 -1
- package/lib/styles/file-upload.d.ts.map +0 -1
- package/lib/styles/file-upload.js.map +0 -1
- package/lib/styles/icon.d.ts.map +0 -1
- package/lib/styles/icon.js.map +0 -1
- package/lib/styles/input.d.ts.map +0 -1
- package/lib/styles/input.js.map +0 -1
- package/lib/styles/modal.d.ts.map +0 -1
- package/lib/styles/modal.js.map +0 -1
- package/lib/styles/multiselect.d.ts +0 -8
- package/lib/styles/multiselect.d.ts.map +0 -1
- package/lib/styles/multiselect.js +0 -849
- package/lib/styles/multiselect.js.map +0 -1
- package/lib/styles/option.d.ts.map +0 -1
- package/lib/styles/option.js.map +0 -1
- package/lib/styles/popup.d.ts.map +0 -1
- package/lib/styles/popup.js.map +0 -1
- package/lib/styles/radio.d.ts.map +0 -1
- package/lib/styles/radio.js.map +0 -1
- package/lib/styles/resize-observer.d.ts.map +0 -1
- package/lib/styles/resize-observer.js.map +0 -1
- package/lib/styles/scroll-container.d.ts.map +0 -1
- package/lib/styles/scroll-container.js.map +0 -1
- package/lib/styles/select-base.d.ts.map +0 -1
- package/lib/styles/select-base.js.map +0 -1
- package/lib/styles/select.d.ts.map +0 -1
- package/lib/styles/select.js.map +0 -1
- package/lib/styles/step.d.ts.map +0 -1
- package/lib/styles/step.js.map +0 -1
- package/lib/styles/switch.d.ts.map +0 -1
- package/lib/styles/switch.js.map +0 -1
- package/lib/styles/tab.d.ts.map +0 -1
- package/lib/styles/tab.js.map +0 -1
- package/lib/styles/tabs.d.ts.map +0 -1
- package/lib/styles/tabs.js.map +0 -1
- package/lib/styles/tag.d.ts.map +0 -1
- package/lib/styles/tag.js.map +0 -1
- package/lib/styles/textarea.d.ts.map +0 -1
- package/lib/styles/textarea.js.map +0 -1
- package/lib/styles/tooltip.d.ts.map +0 -1
- package/lib/styles/tooltip.js.map +0 -1
- package/lib/styles/wizard.d.ts.map +0 -1
- package/lib/styles/wizard.js.map +0 -1
- package/lib/types/common.d.ts.map +0 -1
- package/lib/types/common.js.map +0 -1
- package/lib/utils/calendar-utils.d.ts.map +0 -1
- package/lib/utils/calendar-utils.js.map +0 -1
- package/lib/utils/custom-scrollbar.d.ts.map +0 -1
- package/lib/utils/custom-scrollbar.js.map +0 -1
- package/lib/utils/flavor-sheet.d.ts.map +0 -1
- package/lib/utils/flavor-sheet.js.map +0 -1
- package/lib/utils/icon-registry.d.ts.map +0 -1
- package/lib/utils/icon-registry.js.map +0 -1
- package/lib/utils/icons.d.ts.map +0 -1
- package/lib/utils/icons.js.map +0 -1
- package/lib/utils/loader-registry.d.ts.map +0 -1
- package/lib/utils/loader-registry.js.map +0 -1
- package/lib/utils/locale.d.ts.map +0 -1
- package/lib/utils/locale.js.map +0 -1
- package/lib/utils/mobile.d.ts.map +0 -1
- package/lib/utils/mobile.js.map +0 -1
- package/lib/utils/number-format.d.ts.map +0 -1
- package/lib/utils/number-format.js.map +0 -1
- package/lib/utils/parse-boolean.d.ts +0 -39
- package/lib/utils/parse-boolean.d.ts.map +0 -1
- package/lib/utils/parse-boolean.js +0 -58
- package/lib/utils/parse-boolean.js.map +0 -1
- package/lib/utils/positioning.d.ts.map +0 -1
- package/lib/utils/positioning.js.map +0 -1
- package/lib/utils/property-capture.d.ts.map +0 -1
- package/lib/utils/property-capture.js.map +0 -1
- package/lib/utils/property-manager.d.ts.map +0 -1
- package/lib/utils/property-manager.js.map +0 -1
- package/lib/utils/resize-observer.d.ts.map +0 -1
- package/lib/utils/resize-observer.js.map +0 -1
- package/lib/utils/scroll-lock.d.ts.map +0 -1
- package/lib/utils/scroll-lock.js.map +0 -1
- package/lib/utils/styles.d.ts.map +0 -1
- package/lib/utils/styles.js.map +0 -1
- package/lib/version.d.ts.map +0 -1
- package/lib/version.js.map +0 -1
|
@@ -1,69 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TyDatePicker Web Component
|
|
3
|
-
* PORTED FROM: cljs/ty/components/date_picker.cljs
|
|
4
|
-
*
|
|
5
|
-
* A date picker component with read-only input and calendar dropdown.
|
|
6
|
-
* Supports date-only and date+time modes with smart time input.
|
|
7
|
-
*
|
|
8
|
-
* Architecture:
|
|
9
|
-
* - Read-only input stub (displays formatted date)
|
|
10
|
-
* - Calendar dropdown (modal dialog with ty-calendar)
|
|
11
|
-
* - Optional time input (with smart digit navigation)
|
|
12
|
-
* - Form participation via ElementInternals
|
|
13
|
-
* - UTC output, local display
|
|
14
|
-
*
|
|
15
|
-
* Features:
|
|
16
|
-
* - Date selection with calendar dropdown
|
|
17
|
-
* - Optional time input with smart navigation
|
|
18
|
-
* - Form integration (works with FormData)
|
|
19
|
-
* - UTC value output for server communication
|
|
20
|
-
* - Localized display formatting (Intl API)
|
|
21
|
-
* - Clearable with clear button
|
|
22
|
-
* - Keyboard navigation (Escape to close)
|
|
23
|
-
* - Outside click handling
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```html
|
|
27
|
-
* <!-- Basic date picker -->
|
|
28
|
-
* <ty-date-picker
|
|
29
|
-
* label="Select Date"
|
|
30
|
-
* placeholder="Choose a date">
|
|
31
|
-
* </ty-date-picker>
|
|
32
|
-
*
|
|
33
|
-
* <!-- With time -->
|
|
34
|
-
* <ty-date-picker
|
|
35
|
-
* with-time="true"
|
|
36
|
-
* label="Select Date & Time">
|
|
37
|
-
* </ty-date-picker>
|
|
38
|
-
*
|
|
39
|
-
* <!-- Form integration -->
|
|
40
|
-
* <form>
|
|
41
|
-
* <ty-date-picker name="booking-date"></ty-date-picker>
|
|
42
|
-
* <button type="submit">Submit</button>
|
|
43
|
-
* </form>
|
|
44
|
-
*
|
|
45
|
-
* <!-- Pre-filled value (UTC) -->
|
|
46
|
-
* <ty-date-picker value="2024-09-21T08:30:00.000Z"></ty-date-picker>
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
1
|
import { ensureStyles } from '../utils/styles.js';
|
|
50
2
|
import { syncCustomFlavorSheet } from '../utils/flavor-sheet.js';
|
|
51
3
|
import { datePickerStyles, datePickerCustomFlavorCss } from '../styles/date-picker.js';
|
|
52
4
|
import { lockScroll, unlockScroll } from '../utils/scroll-lock.js';
|
|
53
|
-
import { computeAnchoredPosition } from '../utils/positioning.js';
|
|
5
|
+
import { computeAnchoredPosition, placementToAnchored } from '../utils/positioning.js';
|
|
54
6
|
import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
|
|
55
7
|
import { isMobileTouch } from '../utils/mobile.js';
|
|
56
8
|
import { TyComponent } from '../base/ty-component.js';
|
|
57
|
-
// ============================================================================
|
|
58
|
-
// Constants
|
|
59
|
-
// ============================================================================
|
|
60
9
|
const CALENDAR_ICON_SVG = `<svg stroke='currentColor' fill='none' stroke-width='2' viewBox='0 0 24 24' width='16' height='16' xmlns='http://www.w3.org/2000/svg'><rect x='3' y='4' width='18' height='18' rx='2' ry='2'></rect><line x1='16' y1='2' x2='16' y2='6'></line><line x1='8' y1='2' x2='8' y2='6'></line><line x1='3' y1='10' x2='21' y2='10'></line></svg>`;
|
|
61
10
|
const CLEAR_ICON_SVG = `<svg stroke='currentColor' fill='none' stroke-width='2' viewBox='0 0 24 24' width='14' height='14' xmlns='http://www.w3.org/2000/svg'><line x1='18' y1='6' x2='6' y2='18'></line><line x1='6' y1='6' x2='18' y2='18'></line></svg>`;
|
|
62
11
|
const SCHEDULE_ICON_SVG = `<svg stroke='currentColor' fill='none' stroke-width='2' viewBox='0 0 24 24' width='16' height='16' xmlns='http://www.w3.org/2000/svg'><circle cx='12' cy='12' r='10'></circle><polyline points='12,6 12,12 16,14'></polyline></svg>`;
|
|
63
|
-
// Stable per-instance id (survives across full-rebuild re-renders, unlike a
|
|
64
|
-
// fresh Math.random() per render) — used to wire the stub's aria-labelledby
|
|
65
|
-
// to the label element it's paired with. Same pattern as select.ts's
|
|
66
|
-
// getElementHash.
|
|
67
12
|
const elementIds = new WeakMap();
|
|
68
13
|
let elementIdCounter = 0;
|
|
69
14
|
function getElementHash(element) {
|
|
@@ -74,86 +19,43 @@ function getElementHash(element) {
|
|
|
74
19
|
}
|
|
75
20
|
return id;
|
|
76
21
|
}
|
|
77
|
-
// ============================================================================
|
|
78
|
-
// Helper Functions - Date Parsing & Conversion
|
|
79
|
-
// ============================================================================
|
|
80
|
-
/**
|
|
81
|
-
* Parse ANY input format into year/month/day/hour/minute components.
|
|
82
|
-
* Accepts:
|
|
83
|
-
* - UTC strings: '2024-09-21T08:30:00Z' or '2024-09-21T08:30:00.000Z'
|
|
84
|
-
* - Datetime-local: '2024-09-21T10:30'
|
|
85
|
-
* - Date only: '2024-09-21'
|
|
86
|
-
* - Timestamps: milliseconds since epoch
|
|
87
|
-
* - With timezone: '2024-09-21T10:30:00+02:00'
|
|
88
|
-
*
|
|
89
|
-
* Always extracts components in LOCAL timezone for display/editing.
|
|
90
|
-
*
|
|
91
|
-
* PORTED FROM: parse-value in date_picker.cljs
|
|
92
|
-
*/
|
|
93
22
|
function parseValue(value, withTime) {
|
|
94
23
|
if (!value)
|
|
95
24
|
return null;
|
|
96
25
|
let dateObj = null;
|
|
97
26
|
if (typeof value === 'string') {
|
|
98
|
-
// Date-only format: YYYY-MM-DD
|
|
99
27
|
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
100
28
|
const [yearStr, monthStr, dayStr] = value.split('-');
|
|
101
29
|
const year = parseInt(yearStr, 10);
|
|
102
|
-
const month = parseInt(monthStr, 10) - 1;
|
|
30
|
+
const month = parseInt(monthStr, 10) - 1;
|
|
103
31
|
const day = parseInt(dayStr, 10);
|
|
104
|
-
// Create at midnight local time
|
|
105
32
|
dateObj = new Date(year, month, day, 0, 0, 0, 0);
|
|
106
33
|
}
|
|
107
34
|
else {
|
|
108
|
-
// Let JS Date handle datetime-local, UTC, with timezone
|
|
109
35
|
dateObj = new Date(value);
|
|
110
36
|
}
|
|
111
37
|
}
|
|
112
38
|
else if (typeof value === 'number') {
|
|
113
|
-
// Numeric inputs (milliseconds)
|
|
114
39
|
dateObj = new Date(value);
|
|
115
40
|
}
|
|
116
41
|
if (!dateObj || isNaN(dateObj.getTime())) {
|
|
117
42
|
return null;
|
|
118
43
|
}
|
|
119
|
-
// Extract components in LOCAL timezone for display
|
|
120
44
|
const components = {
|
|
121
45
|
year: dateObj.getFullYear(),
|
|
122
|
-
month: dateObj.getMonth() + 1,
|
|
46
|
+
month: dateObj.getMonth() + 1,
|
|
123
47
|
day: dateObj.getDate(),
|
|
124
48
|
hour: withTime ? dateObj.getHours() : 0,
|
|
125
49
|
minute: withTime ? dateObj.getMinutes() : 0,
|
|
126
50
|
};
|
|
127
51
|
return components;
|
|
128
52
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Convert internal components to Date object.
|
|
131
|
-
*
|
|
132
|
-
* PORTED FROM: components->date-object in date_picker.cljs
|
|
133
|
-
*/
|
|
134
53
|
function componentsToDateObject(components) {
|
|
135
54
|
const { year, month, day, hour, minute } = components;
|
|
136
55
|
if (!year || !month || !day)
|
|
137
56
|
return null;
|
|
138
|
-
|
|
139
|
-
return new Date(year, month - 1, // Convert to 0-based
|
|
140
|
-
day, hour || 0, minute || 0, 0, // seconds
|
|
141
|
-
0 // milliseconds
|
|
142
|
-
);
|
|
57
|
+
return new Date(year, month - 1, day, hour || 0, minute || 0, 0, 0);
|
|
143
58
|
}
|
|
144
|
-
/**
|
|
145
|
-
* Convert internal components to UTC ISO 8601 format.
|
|
146
|
-
*
|
|
147
|
-
* For date+time mode: Outputs full UTC timestamp with milliseconds
|
|
148
|
-
* Example: '2024-09-21T08:30:00.000Z'
|
|
149
|
-
*
|
|
150
|
-
* For date-only mode: Outputs UTC timestamp at midnight local time
|
|
151
|
-
* Example: '2024-09-20T22:00:00.000Z' (midnight Sept 21 CEST = 10pm Sept 20 UTC)
|
|
152
|
-
*
|
|
153
|
-
* Always returns UTC to ensure unambiguous server communication.
|
|
154
|
-
*
|
|
155
|
-
* PORTED FROM: components->output-value in date_picker.cljs
|
|
156
|
-
*/
|
|
157
59
|
function componentsToOutputValue(components) {
|
|
158
60
|
if (!components.year || !components.month || !components.day) {
|
|
159
61
|
return null;
|
|
@@ -161,18 +63,6 @@ function componentsToOutputValue(components) {
|
|
|
161
63
|
const dateObj = componentsToDateObject(components);
|
|
162
64
|
return dateObj ? dateObj.toISOString() : null;
|
|
163
65
|
}
|
|
164
|
-
/**
|
|
165
|
-
* Convert internal components to local datetime-local format.
|
|
166
|
-
*
|
|
167
|
-
* For date+time mode: Outputs local datetime without timezone
|
|
168
|
-
* Example: '2024-09-21T10:30'
|
|
169
|
-
*
|
|
170
|
-
* For date-only mode: Outputs date only
|
|
171
|
-
* Example: '2024-09-21'
|
|
172
|
-
*
|
|
173
|
-
* This format matches HTML5 <input type="datetime-local"> and is useful
|
|
174
|
-
* for setting other inputs or displaying local time without timezone conversion.
|
|
175
|
-
*/
|
|
176
66
|
function componentsToLocalValue(components, withTime) {
|
|
177
67
|
if (!components.year || !components.month || !components.day) {
|
|
178
68
|
return null;
|
|
@@ -189,11 +79,6 @@ function componentsToLocalValue(components, withTime) {
|
|
|
189
79
|
return `${year}-${month}-${day}`;
|
|
190
80
|
}
|
|
191
81
|
}
|
|
192
|
-
/**
|
|
193
|
-
* Format components for display in input using Intl API.
|
|
194
|
-
*
|
|
195
|
-
* PORTED FROM: format-display-value in date_picker.cljs
|
|
196
|
-
*/
|
|
197
82
|
function formatDisplayValue(components, formatType, locale, withTime) {
|
|
198
83
|
if (!components.year || !components.month || !components.day) {
|
|
199
84
|
return null;
|
|
@@ -204,21 +89,12 @@ function formatDisplayValue(components, formatType, locale, withTime) {
|
|
|
204
89
|
const options = {
|
|
205
90
|
dateStyle: formatType || 'long',
|
|
206
91
|
};
|
|
207
|
-
// Add time styling if with-time is enabled
|
|
208
92
|
if (withTime) {
|
|
209
93
|
options.timeStyle = 'short';
|
|
210
94
|
}
|
|
211
95
|
const formatter = new Intl.DateTimeFormat(locale, options);
|
|
212
96
|
return formatter.format(dateObj);
|
|
213
97
|
}
|
|
214
|
-
// ============================================================================
|
|
215
|
-
// Helper Functions - Time Input
|
|
216
|
-
// ============================================================================
|
|
217
|
-
/**
|
|
218
|
-
* Parse hour and minute from raw digits (4 chars: "HHmm")
|
|
219
|
-
*
|
|
220
|
-
* PORTED FROM: parse-time-components in date_picker.cljs
|
|
221
|
-
*/
|
|
222
98
|
function parseTimeComponents(rawDigits) {
|
|
223
99
|
if (!rawDigits || rawDigits.length !== 4)
|
|
224
100
|
return null;
|
|
@@ -231,99 +107,60 @@ function parseTimeComponents(rawDigits) {
|
|
|
231
107
|
}
|
|
232
108
|
return { hour, minute };
|
|
233
109
|
}
|
|
234
|
-
/**
|
|
235
|
-
* Validate if digit is valid for given position
|
|
236
|
-
* Position 0: first hour digit (0-2)
|
|
237
|
-
* Position 1: second hour digit (0-9, but 0-3 if first is 2)
|
|
238
|
-
* Position 3: first minute digit (0-5)
|
|
239
|
-
* Position 4: second minute digit (0-9)
|
|
240
|
-
*
|
|
241
|
-
* PORTED FROM: validate-time-digit in date_picker.cljs
|
|
242
|
-
*/
|
|
243
110
|
function validateTimeDigit(digit, position, currentDigits) {
|
|
244
111
|
switch (position) {
|
|
245
112
|
case 0:
|
|
246
|
-
return digit <= 2;
|
|
113
|
+
return digit <= 2;
|
|
247
114
|
case 1: {
|
|
248
115
|
const firstHour = parseInt(currentDigits[0], 10);
|
|
249
|
-
return firstHour === 2 ? digit <= 3 : true;
|
|
116
|
+
return firstHour === 2 ? digit <= 3 : true;
|
|
250
117
|
}
|
|
251
118
|
case 3:
|
|
252
|
-
return digit <= 5;
|
|
119
|
+
return digit <= 5;
|
|
253
120
|
case 4:
|
|
254
|
-
return true;
|
|
121
|
+
return true;
|
|
255
122
|
default:
|
|
256
123
|
return false;
|
|
257
124
|
}
|
|
258
125
|
}
|
|
259
|
-
/**
|
|
260
|
-
* Format hour and minute into "HH:mm" display
|
|
261
|
-
*
|
|
262
|
-
* PORTED FROM: format-time-display in date_picker.cljs
|
|
263
|
-
*/
|
|
264
126
|
function formatTimeDisplay(hour, minute) {
|
|
265
127
|
const hourStr = hour.toString().padStart(2, '0');
|
|
266
128
|
const minuteStr = minute.toString().padStart(2, '0');
|
|
267
129
|
return `${hourStr}:${minuteStr}`;
|
|
268
130
|
}
|
|
269
|
-
/**
|
|
270
|
-
* Find next editable position, skipping delimiter at position 2
|
|
271
|
-
*
|
|
272
|
-
* PORTED FROM: find-next-editable-position in date_picker.cljs
|
|
273
|
-
*/
|
|
274
131
|
function findNextEditablePosition(currentPos) {
|
|
275
132
|
switch (currentPos) {
|
|
276
|
-
case 0: return 1;
|
|
277
|
-
case 1: return 3;
|
|
278
|
-
case 3: return 4;
|
|
279
|
-
case 4: return 5;
|
|
280
|
-
case 5: return 5;
|
|
133
|
+
case 0: return 1;
|
|
134
|
+
case 1: return 3;
|
|
135
|
+
case 3: return 4;
|
|
136
|
+
case 4: return 5;
|
|
137
|
+
case 5: return 5;
|
|
281
138
|
default: return currentPos;
|
|
282
139
|
}
|
|
283
140
|
}
|
|
284
|
-
/**
|
|
285
|
-
* Find previous editable position, skipping delimiter at position 2
|
|
286
|
-
*
|
|
287
|
-
* PORTED FROM: find-prev-editable-position in date_picker.cljs
|
|
288
|
-
*/
|
|
289
141
|
function findPrevEditablePosition(currentPos) {
|
|
290
142
|
switch (currentPos) {
|
|
291
|
-
case 5: return 4;
|
|
292
|
-
case 4: return 3;
|
|
293
|
-
case 3: return 1;
|
|
294
|
-
case 1: return 0;
|
|
295
|
-
case 0: return 0;
|
|
143
|
+
case 5: return 4;
|
|
144
|
+
case 4: return 3;
|
|
145
|
+
case 3: return 1;
|
|
146
|
+
case 1: return 0;
|
|
147
|
+
case 0: return 0;
|
|
296
148
|
default: return currentPos;
|
|
297
149
|
}
|
|
298
150
|
}
|
|
299
|
-
/**
|
|
300
|
-
* Convert internal position (0,1,3,4) to raw digits index (0,1,2,3)
|
|
301
|
-
*
|
|
302
|
-
* PORTED FROM: position->raw-digit-index in date_picker.cljs
|
|
303
|
-
*/
|
|
304
151
|
function positionToRawDigitIndex(internalPos) {
|
|
305
152
|
switch (internalPos) {
|
|
306
|
-
case 0: return 0;
|
|
307
|
-
case 1: return 1;
|
|
308
|
-
case 3: return 2;
|
|
309
|
-
case 4: return 3;
|
|
153
|
+
case 0: return 0;
|
|
154
|
+
case 1: return 1;
|
|
155
|
+
case 3: return 2;
|
|
156
|
+
case 4: return 3;
|
|
310
157
|
default: return 0;
|
|
311
158
|
}
|
|
312
159
|
}
|
|
313
|
-
// ============================================================================
|
|
314
|
-
// TimeInput Helper Class
|
|
315
|
-
// ============================================================================
|
|
316
|
-
/**
|
|
317
|
-
* TimeInput manages the state and behavior of the time input element.
|
|
318
|
-
* Handles smart cursor navigation, digit replacement, and validation.
|
|
319
|
-
*
|
|
320
|
-
* PORTED FROM: Time input state management functions in date_picker.cljs
|
|
321
|
-
*/
|
|
322
160
|
class TimeInput {
|
|
323
161
|
constructor(element, datePickerElement, hour = 0, minute = 0) {
|
|
324
162
|
this.element = element;
|
|
325
163
|
this.datePickerElement = datePickerElement;
|
|
326
|
-
// Initialize state
|
|
327
164
|
const display = formatTimeDisplay(hour, minute);
|
|
328
165
|
const rawDigits = hour.toString().padStart(2, '0') + minute.toString().padStart(2, '0');
|
|
329
166
|
this.state = {
|
|
@@ -333,23 +170,15 @@ class TimeInput {
|
|
|
333
170
|
displayValue: display,
|
|
334
171
|
rawDigits,
|
|
335
172
|
};
|
|
336
|
-
// Set initial value
|
|
337
173
|
this.element.value = display;
|
|
338
|
-
// Bind event handlers
|
|
339
174
|
this.setupEventListeners();
|
|
340
175
|
}
|
|
341
|
-
/**
|
|
342
|
-
* Setup all event listeners for the time input
|
|
343
|
-
*/
|
|
344
176
|
setupEventListeners() {
|
|
345
177
|
this.element.addEventListener('keydown', (e) => this.handleKeyDown(e));
|
|
346
178
|
this.element.addEventListener('input', (e) => this.handleInput(e));
|
|
347
179
|
this.element.addEventListener('click', () => this.handleClick());
|
|
348
180
|
this.element.addEventListener('focus', () => this.handleFocus());
|
|
349
181
|
}
|
|
350
|
-
/**
|
|
351
|
-
* Handle keydown events
|
|
352
|
-
*/
|
|
353
182
|
handleKeyDown(event) {
|
|
354
183
|
const key = event.key;
|
|
355
184
|
switch (key) {
|
|
@@ -374,89 +203,52 @@ class TimeInput {
|
|
|
374
203
|
this.updateState({ caretPosition: 5 });
|
|
375
204
|
break;
|
|
376
205
|
case 'Tab':
|
|
377
|
-
// Allow default tab behavior
|
|
378
206
|
break;
|
|
379
207
|
default:
|
|
380
|
-
// Handle digit input
|
|
381
208
|
if (/^\d$/.test(key)) {
|
|
382
209
|
this.handleDigitInput(event, parseInt(key, 10));
|
|
383
210
|
}
|
|
384
211
|
break;
|
|
385
212
|
}
|
|
386
213
|
}
|
|
387
|
-
/**
|
|
388
|
-
* Handle input events (prevent default browser input)
|
|
389
|
-
*/
|
|
390
214
|
handleInput(event) {
|
|
391
215
|
event.preventDefault();
|
|
392
216
|
}
|
|
393
|
-
/**
|
|
394
|
-
* Handle click events - position cursor at first digit
|
|
395
|
-
*/
|
|
396
217
|
handleClick() {
|
|
397
218
|
this.updateState({ caretPosition: 0 });
|
|
398
219
|
}
|
|
399
|
-
/**
|
|
400
|
-
* Handle focus events - position cursor at first digit
|
|
401
|
-
*/
|
|
402
220
|
handleFocus() {
|
|
403
221
|
this.updateState({ caretPosition: 0 });
|
|
404
222
|
}
|
|
405
|
-
/**
|
|
406
|
-
* Handle arrow right - move to next editable position
|
|
407
|
-
*
|
|
408
|
-
* PORTED FROM: handle-time-arrow-right! in date_picker.cljs
|
|
409
|
-
*/
|
|
410
223
|
handleArrowRight(event) {
|
|
411
224
|
event.preventDefault();
|
|
412
225
|
const nextPos = findNextEditablePosition(this.state.caretPosition);
|
|
413
226
|
this.updateState({ caretPosition: nextPos });
|
|
414
227
|
}
|
|
415
|
-
/**
|
|
416
|
-
* Handle arrow left - move to previous editable position
|
|
417
|
-
*
|
|
418
|
-
* PORTED FROM: handle-time-arrow-left! in date_picker.cljs
|
|
419
|
-
*/
|
|
420
228
|
handleArrowLeft(event) {
|
|
421
229
|
event.preventDefault();
|
|
422
230
|
const prevPos = findPrevEditablePosition(this.state.caretPosition);
|
|
423
231
|
this.updateState({ caretPosition: prevPos });
|
|
424
232
|
}
|
|
425
|
-
/**
|
|
426
|
-
* Handle digit input - replace digit at cursor position
|
|
427
|
-
*
|
|
428
|
-
* PORTED FROM: handle-time-digit-input! in date_picker.cljs
|
|
429
|
-
*/
|
|
430
233
|
handleDigitInput(event, digit) {
|
|
431
234
|
event.preventDefault();
|
|
432
235
|
const currentPos = this.state.caretPosition;
|
|
433
|
-
// Only process if at editable position and digit is valid
|
|
434
236
|
if (![0, 1, 3, 4].includes(currentPos))
|
|
435
237
|
return;
|
|
436
238
|
if (!validateTimeDigit(digit, currentPos, this.state.rawDigits))
|
|
437
239
|
return;
|
|
438
|
-
// Replace digit at current position
|
|
439
240
|
const newState = this.replaceDigitAtPosition(currentPos, digit);
|
|
440
241
|
if (!newState)
|
|
441
242
|
return;
|
|
442
|
-
// Move to next position
|
|
443
243
|
const nextPos = findNextEditablePosition(currentPos);
|
|
444
244
|
this.updateState({ ...newState, caretPosition: nextPos });
|
|
445
|
-
// Notify date picker of time change
|
|
446
245
|
this.notifyTimeChange();
|
|
447
246
|
}
|
|
448
|
-
/**
|
|
449
|
-
* Handle backspace - zero digit and move back
|
|
450
|
-
*
|
|
451
|
-
* PORTED FROM: handle-time-backspace! in date_picker.cljs
|
|
452
|
-
*/
|
|
453
247
|
handleBackspace(event) {
|
|
454
248
|
event.preventDefault();
|
|
455
249
|
const currentPos = this.state.caretPosition;
|
|
456
|
-
// Can't go back from position 0
|
|
457
250
|
if (currentPos === 0)
|
|
458
251
|
return;
|
|
459
|
-
// Find target position to zero
|
|
460
252
|
const targetPos = currentPos === 1 ? 0 :
|
|
461
253
|
currentPos === 3 ? 1 :
|
|
462
254
|
currentPos === 4 ? 3 :
|
|
@@ -467,15 +259,9 @@ class TimeInput {
|
|
|
467
259
|
this.updateState({ ...newState, caretPosition: targetPos });
|
|
468
260
|
this.notifyTimeChange();
|
|
469
261
|
}
|
|
470
|
-
/**
|
|
471
|
-
* Handle delete - zero digit at current position
|
|
472
|
-
*
|
|
473
|
-
* PORTED FROM: handle-time-delete! in date_picker.cljs
|
|
474
|
-
*/
|
|
475
262
|
handleDelete(event) {
|
|
476
263
|
event.preventDefault();
|
|
477
264
|
const currentPos = this.state.caretPosition;
|
|
478
|
-
// Only at editable positions
|
|
479
265
|
if (![0, 1, 3, 4].includes(currentPos))
|
|
480
266
|
return;
|
|
481
267
|
const newState = this.zeroDigitAtPosition(currentPos);
|
|
@@ -484,11 +270,6 @@ class TimeInput {
|
|
|
484
270
|
this.updateState({ ...newState, caretPosition: currentPos });
|
|
485
271
|
this.notifyTimeChange();
|
|
486
272
|
}
|
|
487
|
-
/**
|
|
488
|
-
* Replace digit at specific position
|
|
489
|
-
*
|
|
490
|
-
* PORTED FROM: replace-digit-at-position in date_picker.cljs
|
|
491
|
-
*/
|
|
492
273
|
replaceDigitAtPosition(position, newDigit) {
|
|
493
274
|
const rawIndex = positionToRawDigitIndex(position);
|
|
494
275
|
const digitsArray = this.state.rawDigits.split('');
|
|
@@ -504,24 +285,12 @@ class TimeInput {
|
|
|
504
285
|
displayValue: formatTimeDisplay(parsed.hour, parsed.minute),
|
|
505
286
|
};
|
|
506
287
|
}
|
|
507
|
-
/**
|
|
508
|
-
* Zero digit at specific position
|
|
509
|
-
*
|
|
510
|
-
* PORTED FROM: zero-digit-at-position in date_picker.cljs
|
|
511
|
-
*/
|
|
512
288
|
zeroDigitAtPosition(position) {
|
|
513
289
|
return this.replaceDigitAtPosition(position, 0);
|
|
514
290
|
}
|
|
515
|
-
/**
|
|
516
|
-
* Update internal state and refresh display
|
|
517
|
-
*
|
|
518
|
-
* PORTED FROM: update-time-input-state! in date_picker.cljs
|
|
519
|
-
*/
|
|
520
291
|
updateState(updates) {
|
|
521
292
|
this.state = { ...this.state, ...updates };
|
|
522
|
-
// Update display value
|
|
523
293
|
this.element.value = this.state.displayValue;
|
|
524
|
-
// Set cursor position, mapping internal positions to DOM positions
|
|
525
294
|
const caretPos = this.state.caretPosition;
|
|
526
295
|
const actualPos = caretPos === 0 ? 0 :
|
|
527
296
|
caretPos === 1 ? 1 :
|
|
@@ -531,24 +300,15 @@ class TimeInput {
|
|
|
531
300
|
caretPos === 5 ? 5 : caretPos;
|
|
532
301
|
this.element.setSelectionRange(actualPos, actualPos);
|
|
533
302
|
}
|
|
534
|
-
/**
|
|
535
|
-
* Notify date picker of time change
|
|
536
|
-
*/
|
|
537
303
|
notifyTimeChange() {
|
|
538
304
|
this.datePickerElement.handleTimeInputChange(this.state.hour, this.state.minute);
|
|
539
305
|
}
|
|
540
|
-
/**
|
|
541
|
-
* Get current time values
|
|
542
|
-
*/
|
|
543
306
|
getTime() {
|
|
544
307
|
return {
|
|
545
308
|
hour: this.state.hour,
|
|
546
309
|
minute: this.state.minute,
|
|
547
310
|
};
|
|
548
311
|
}
|
|
549
|
-
/**
|
|
550
|
-
* Update time from external source
|
|
551
|
-
*/
|
|
552
312
|
setTime(hour, minute) {
|
|
553
313
|
const display = formatTimeDisplay(hour, minute);
|
|
554
314
|
const rawDigits = hour.toString().padStart(2, '0') + minute.toString().padStart(2, '0');
|
|
@@ -562,76 +322,43 @@ class TimeInput {
|
|
|
562
322
|
this.element.value = display;
|
|
563
323
|
}
|
|
564
324
|
}
|
|
565
|
-
// ============================================================================
|
|
566
|
-
// TyDatePicker Custom Element
|
|
567
|
-
// ============================================================================
|
|
568
325
|
export class TyDatePicker extends TyComponent {
|
|
569
|
-
// observedAttributes is auto-generated by TyComponent from properties config
|
|
570
326
|
constructor() {
|
|
571
|
-
super();
|
|
572
|
-
// ============================================================================
|
|
573
|
-
// INTERNAL STATE
|
|
574
|
-
// ============================================================================
|
|
575
|
-
// Internal state
|
|
327
|
+
super();
|
|
576
328
|
this._state = {
|
|
577
329
|
withTime: false,
|
|
578
330
|
open: false,
|
|
579
331
|
};
|
|
580
|
-
// Reopen guard — prevents click event from reopening after pointerdown close
|
|
581
332
|
this._closeTimestamp = 0;
|
|
582
333
|
this._customFlavorSheet = null;
|
|
583
|
-
// Initialize styles in shadow root
|
|
584
334
|
const shadow = this.shadowRoot;
|
|
585
335
|
ensureStyles(shadow, { css: datePickerStyles, id: 'ty-date-picker' });
|
|
586
336
|
}
|
|
587
|
-
// ==========================================================================
|
|
588
|
-
// Lifecycle Hooks (TyComponent)
|
|
589
|
-
// ==========================================================================
|
|
590
|
-
/**
|
|
591
|
-
* Called when component connects to DOM
|
|
592
|
-
* TyComponent handles property capture automatically
|
|
593
|
-
*/
|
|
594
337
|
onConnect() {
|
|
595
|
-
// Initialize component state
|
|
596
338
|
this.initializeState();
|
|
597
|
-
// Initial render
|
|
598
339
|
this.render();
|
|
599
|
-
// Setup locale observer to watch for ancestor lang changes
|
|
600
340
|
this._localeObserver = observeLocaleChanges(this, () => {
|
|
601
341
|
this.render();
|
|
602
342
|
});
|
|
603
343
|
this._syncCustomFlavor();
|
|
604
344
|
}
|
|
605
|
-
/** Custom (non-built-in) flavors — see utils/flavor-sheet.ts. */
|
|
606
345
|
_syncCustomFlavor() {
|
|
607
|
-
// 'default' is this component's neutral: map it to a built-in so no
|
|
608
|
-
// custom sheet is generated for it.
|
|
609
346
|
const flavor = this.flavor === 'default' ? 'neutral' : this.flavor;
|
|
610
347
|
this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFlavorSheet, flavor, ({ base }) => datePickerCustomFlavorCss(base));
|
|
611
348
|
}
|
|
612
|
-
/**
|
|
613
|
-
* Called when component disconnects from DOM
|
|
614
|
-
*/
|
|
615
349
|
onDisconnect() {
|
|
616
|
-
// Cleanup locale observer
|
|
617
350
|
if (this._localeObserver) {
|
|
618
351
|
this._localeObserver();
|
|
619
352
|
this._localeObserver = undefined;
|
|
620
353
|
}
|
|
621
|
-
// Cleanup event listeners and state
|
|
622
354
|
this.cleanup();
|
|
623
355
|
}
|
|
624
|
-
/**
|
|
625
|
-
* Called when properties change
|
|
626
|
-
* Handle state updates BEFORE render
|
|
627
|
-
*/
|
|
628
356
|
onPropertiesChanged(changes) {
|
|
629
357
|
for (const { name, newValue } of changes) {
|
|
630
358
|
switch (name) {
|
|
631
359
|
case 'flavor':
|
|
632
360
|
this._syncCustomFlavor();
|
|
633
361
|
break;
|
|
634
|
-
// Simple visual properties - just trigger re-render (handled by TyComponent)
|
|
635
362
|
case 'size':
|
|
636
363
|
case 'label':
|
|
637
364
|
case 'placeholder':
|
|
@@ -640,30 +367,21 @@ export class TyDatePicker extends TyComponent {
|
|
|
640
367
|
case 'clearable':
|
|
641
368
|
case 'format':
|
|
642
369
|
case 'locale':
|
|
643
|
-
// These properties just affect rendering, no internal state to update
|
|
644
|
-
// TyComponent will call render() automatically for visual properties
|
|
645
370
|
break;
|
|
646
|
-
// Complex properties
|
|
647
371
|
case 'value': {
|
|
648
|
-
// Parse the new value using current withTime setting
|
|
649
372
|
const newComponents = parseValue(newValue, this._state.withTime);
|
|
650
|
-
// If newComponents is null, CLEAR the state completely
|
|
651
373
|
if (newComponents === null) {
|
|
652
|
-
// Check if we actually have a date to clear
|
|
653
374
|
const hasDate = this._state.year !== undefined ||
|
|
654
375
|
this._state.month !== undefined ||
|
|
655
376
|
this._state.day !== undefined;
|
|
656
377
|
if (hasDate) {
|
|
657
|
-
// Clear all date components, keeping only withTime and open flags
|
|
658
378
|
this._state = {
|
|
659
379
|
withTime: this._state.withTime,
|
|
660
380
|
open: this._state.open,
|
|
661
|
-
// year, month, day, hour, minute are now undefined
|
|
662
381
|
};
|
|
663
382
|
}
|
|
664
383
|
break;
|
|
665
384
|
}
|
|
666
|
-
// Check if components actually changed
|
|
667
385
|
const currentComponents = {
|
|
668
386
|
year: this._state.year,
|
|
669
387
|
month: this._state.month,
|
|
@@ -677,7 +395,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
677
395
|
newComponents?.hour !== currentComponents.hour ||
|
|
678
396
|
newComponents?.minute !== currentComponents.minute;
|
|
679
397
|
if (changed) {
|
|
680
|
-
// Update state with new date components
|
|
681
398
|
this._state = {
|
|
682
399
|
...this._state,
|
|
683
400
|
...newComponents,
|
|
@@ -686,13 +403,10 @@ export class TyDatePicker extends TyComponent {
|
|
|
686
403
|
break;
|
|
687
404
|
}
|
|
688
405
|
case 'with-time': {
|
|
689
|
-
// Update internal withTime flag
|
|
690
406
|
const newWithTime = newValue;
|
|
691
407
|
const oldWithTime = this._state.withTime;
|
|
692
408
|
if (newWithTime !== oldWithTime) {
|
|
693
409
|
this._state.withTime = newWithTime;
|
|
694
|
-
// If we have an existing date, re-sync form value
|
|
695
|
-
// (format changes based on withTime flag)
|
|
696
410
|
const hasDate = this._state.year !== undefined &&
|
|
697
411
|
this._state.month !== undefined &&
|
|
698
412
|
this._state.day !== undefined;
|
|
@@ -705,20 +419,9 @@ export class TyDatePicker extends TyComponent {
|
|
|
705
419
|
}
|
|
706
420
|
}
|
|
707
421
|
}
|
|
708
|
-
// attributeChangedCallback removed - TyComponent handles all attribute changes via onPropertiesChanged
|
|
709
|
-
/**
|
|
710
|
-
* Get form value - returns UTC string from current state
|
|
711
|
-
* TyComponent calls this automatically when value property changes
|
|
712
|
-
*/
|
|
713
422
|
getFormValue() {
|
|
714
423
|
return componentsToOutputValue(this.getComponents());
|
|
715
424
|
}
|
|
716
|
-
// ==========================================================================
|
|
717
|
-
// Internal Helpers
|
|
718
|
-
// ==========================================================================
|
|
719
|
-
/**
|
|
720
|
-
* Extract current date components from internal state
|
|
721
|
-
*/
|
|
722
425
|
getComponents() {
|
|
723
426
|
return {
|
|
724
427
|
year: this._state.year,
|
|
@@ -728,34 +431,19 @@ export class TyDatePicker extends TyComponent {
|
|
|
728
431
|
minute: this._state.minute,
|
|
729
432
|
};
|
|
730
433
|
}
|
|
731
|
-
/**
|
|
732
|
-
* Whether internal state has a complete date
|
|
733
|
-
*/
|
|
734
434
|
hasDate() {
|
|
735
435
|
return !!(this._state.year && this._state.month && this._state.day);
|
|
736
436
|
}
|
|
737
|
-
/**
|
|
738
|
-
* Format current components for display using Intl API
|
|
739
|
-
*/
|
|
740
437
|
getFormattedValue(components) {
|
|
741
438
|
const c = components || this.getComponents();
|
|
742
439
|
if (!c.year || !c.month || !c.day)
|
|
743
440
|
return null;
|
|
744
441
|
return formatDisplayValue(c, this.getProperty('format') || 'long', getEffectiveLocale(this, this.getProperty('locale')), this._state.withTime);
|
|
745
442
|
}
|
|
746
|
-
// ==========================================================================
|
|
747
|
-
// State Management
|
|
748
|
-
// ==========================================================================
|
|
749
|
-
/**
|
|
750
|
-
* Initialize component state from attributes
|
|
751
|
-
*
|
|
752
|
-
* PORTED FROM: init-component-state! in date_picker.cljs
|
|
753
|
-
*/
|
|
754
443
|
initializeState() {
|
|
755
444
|
const valueAttr = this.getProperty('value');
|
|
756
445
|
const withTime = this.getProperty('with-time');
|
|
757
446
|
const components = parseValue(valueAttr, withTime);
|
|
758
|
-
// If components is null, initialize with empty state
|
|
759
447
|
if (components === null) {
|
|
760
448
|
this._state = {
|
|
761
449
|
withTime,
|
|
@@ -769,34 +457,19 @@ export class TyDatePicker extends TyComponent {
|
|
|
769
457
|
open: false,
|
|
770
458
|
};
|
|
771
459
|
}
|
|
772
|
-
// Convert to UTC and sync to form
|
|
773
460
|
this.syncFormValue();
|
|
774
461
|
}
|
|
775
|
-
/**
|
|
776
|
-
* Update internal state
|
|
777
|
-
*
|
|
778
|
-
* PORTED FROM: update-component-state! in date_picker.cljs
|
|
779
|
-
*/
|
|
780
462
|
updateState(updates, forceSync = false) {
|
|
781
463
|
this._state = { ...this._state, ...updates };
|
|
782
|
-
// STAGING: Only sync attributes if dialog is closed OR force-sync is true
|
|
783
|
-
// This prevents re-renders during time input editing
|
|
784
464
|
const shouldSync = forceSync || !this._state.open || !this._state.withTime;
|
|
785
465
|
if (shouldSync) {
|
|
786
466
|
this.syncFormValue();
|
|
787
467
|
}
|
|
788
468
|
}
|
|
789
|
-
/**
|
|
790
|
-
* Sync form value with current state
|
|
791
|
-
* Compares before setting to prevent circular triggers
|
|
792
|
-
*/
|
|
793
469
|
syncFormValue() {
|
|
794
470
|
const utcValue = this.getFormValue();
|
|
795
471
|
const currentValue = this.getProperty('value');
|
|
796
|
-
// Only update property if value actually changed (prevents circular triggers)
|
|
797
472
|
if (utcValue !== currentValue) {
|
|
798
|
-
// Use property setter to maintain TyComponent lifecycle
|
|
799
|
-
// This will automatically handle attribute sync and form value update
|
|
800
473
|
if (utcValue) {
|
|
801
474
|
this.setProperty('value', utcValue);
|
|
802
475
|
}
|
|
@@ -804,17 +477,7 @@ export class TyDatePicker extends TyComponent {
|
|
|
804
477
|
this.setProperty('value', null);
|
|
805
478
|
}
|
|
806
479
|
}
|
|
807
|
-
|
|
808
|
-
}
|
|
809
|
-
// handleValueChange removed - logic moved to onPropertiesChanged hook
|
|
810
|
-
// ==========================================================================
|
|
811
|
-
// Time Input Handling
|
|
812
|
-
// ==========================================================================
|
|
813
|
-
/**
|
|
814
|
-
* Handle time input changes from TimeInput class
|
|
815
|
-
*
|
|
816
|
-
* PORTED FROM: handle-time-change! in date_picker.cljs
|
|
817
|
-
*/
|
|
480
|
+
}
|
|
818
481
|
handleTimeInputChange(hour, minute) {
|
|
819
482
|
if (!this.hasDate())
|
|
820
483
|
return;
|
|
@@ -822,11 +485,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
822
485
|
this.updateState(components, true);
|
|
823
486
|
this.emitChangeEvent(components, 'time-change');
|
|
824
487
|
}
|
|
825
|
-
/**
|
|
826
|
-
* Emit change event
|
|
827
|
-
*
|
|
828
|
-
* PORTED FROM: emit-change-event! in date_picker.cljs
|
|
829
|
-
*/
|
|
830
488
|
emitChangeEvent(components, source) {
|
|
831
489
|
const utcValue = components ? componentsToOutputValue(components) : null;
|
|
832
490
|
const localValue = components ? componentsToLocalValue(components, this._state.withTime) : null;
|
|
@@ -846,12 +504,10 @@ export class TyDatePicker extends TyComponent {
|
|
|
846
504
|
});
|
|
847
505
|
this.dispatchEvent(event);
|
|
848
506
|
}
|
|
849
|
-
// ==========================================================================
|
|
850
|
-
// Public API (Properties) - Simple Accessors
|
|
851
|
-
// ==========================================================================
|
|
852
|
-
// String properties
|
|
853
507
|
get size() { return this.getProperty('size'); }
|
|
854
508
|
set size(v) { this.setProperty('size', v); }
|
|
509
|
+
get placement() { return this.getProperty('placement'); }
|
|
510
|
+
set placement(v) { this.setProperty('placement', v); }
|
|
855
511
|
get flavor() { return this.getProperty('flavor'); }
|
|
856
512
|
set flavor(v) { this.setProperty('flavor', v); }
|
|
857
513
|
get label() { return this.getProperty('label'); }
|
|
@@ -864,7 +520,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
864
520
|
set format(v) { this.setProperty('format', v); }
|
|
865
521
|
get locale() { return this.getProperty('locale'); }
|
|
866
522
|
set locale(v) { this.setProperty('locale', v); }
|
|
867
|
-
// Boolean properties
|
|
868
523
|
get required() { return this.getProperty('required'); }
|
|
869
524
|
set required(v) { this.setProperty('required', v); }
|
|
870
525
|
get disabled() { return this.getProperty('disabled'); }
|
|
@@ -875,48 +530,23 @@ export class TyDatePicker extends TyComponent {
|
|
|
875
530
|
set min(v) { this.setProperty('min', v ?? ''); }
|
|
876
531
|
get max() { return this.getProperty('max'); }
|
|
877
532
|
set max(v) { this.setProperty('max', v ?? ''); }
|
|
878
|
-
// With-time property
|
|
879
533
|
get withTime() { return this.getProperty('with-time'); }
|
|
880
534
|
set withTime(v) { this.setProperty('with-time', v); }
|
|
881
|
-
// Value property - Keep custom getter/setter for now (complex UTC parsing logic)
|
|
882
|
-
/**
|
|
883
|
-
* Get current value (UTC ISO string)
|
|
884
|
-
*/
|
|
885
535
|
get value() {
|
|
886
536
|
return this.getProperty('value') || null;
|
|
887
537
|
}
|
|
888
|
-
/**
|
|
889
|
-
* Set value (UTC ISO string, Date object, or null)
|
|
890
|
-
*
|
|
891
|
-
* When set to null/undefined/empty string, the attribute is removed.
|
|
892
|
-
* When set to a valid date, the attribute is set to ISO UTC string.
|
|
893
|
-
*/
|
|
894
538
|
set value(val) {
|
|
895
539
|
if (val === null || val === undefined || val === '') {
|
|
896
|
-
this.setProperty('value', null);
|
|
540
|
+
this.setProperty('value', null);
|
|
897
541
|
}
|
|
898
542
|
else {
|
|
899
543
|
const strValue = val instanceof Date ? val.toISOString() : val;
|
|
900
544
|
this.setProperty('value', strValue);
|
|
901
545
|
}
|
|
902
546
|
}
|
|
903
|
-
/**
|
|
904
|
-
* Clear the date value programmatically WITH a change event (source:
|
|
905
|
-
* 'clear'). Works regardless of the `clearable` attribute — lets any
|
|
906
|
-
* external trigger (e.g. a button elsewhere in the page) clear the value
|
|
907
|
-
* without needing the built-in clear button. Mirrors ty-select's clear().
|
|
908
|
-
*/
|
|
909
547
|
clear() {
|
|
910
548
|
this.clearValue();
|
|
911
549
|
}
|
|
912
|
-
// ==========================================================================
|
|
913
|
-
// Rendering & DOM Management
|
|
914
|
-
// ==========================================================================
|
|
915
|
-
/**
|
|
916
|
-
* Build CSS classes for the stub element
|
|
917
|
-
*
|
|
918
|
-
* PORTED FROM: build-stub-classes in date_picker.cljs
|
|
919
|
-
*/
|
|
920
550
|
buildStubClasses() {
|
|
921
551
|
const classes = ['date-picker-stub'];
|
|
922
552
|
const size = this.getProperty('size') || 'md';
|
|
@@ -929,11 +559,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
929
559
|
classes.push('open');
|
|
930
560
|
return classes.join(' ');
|
|
931
561
|
}
|
|
932
|
-
/**
|
|
933
|
-
* Render the date picker stub (input display)
|
|
934
|
-
*
|
|
935
|
-
* PORTED FROM: render-date-picker-stub in date_picker.cljs
|
|
936
|
-
*/
|
|
937
562
|
renderStub() {
|
|
938
563
|
const stub = document.createElement('div');
|
|
939
564
|
stub.className = this.buildStubClasses();
|
|
@@ -941,11 +566,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
941
566
|
if (isDisabled) {
|
|
942
567
|
stub.setAttribute('disabled', 'true');
|
|
943
568
|
}
|
|
944
|
-
// Not text-editable (click/Enter/Space just opens the calendar dialog),
|
|
945
|
-
// so role="button" — not role="combobox", which implies typing. The
|
|
946
|
-
// popup is a real <dialog> opened via showModal() (see openDropdown),
|
|
947
|
-
// so it already gets role="dialog" + focus-trap from the browser for
|
|
948
|
-
// free; only the trigger itself needs wiring.
|
|
949
569
|
stub.setAttribute('role', 'button');
|
|
950
570
|
stub.setAttribute('aria-haspopup', 'dialog');
|
|
951
571
|
stub.setAttribute('aria-expanded', String(this._state.open));
|
|
@@ -955,9 +575,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
955
575
|
if (this.getProperty('label')) {
|
|
956
576
|
stub.setAttribute('aria-labelledby', `date-picker-label-${getElementHash(this)}`);
|
|
957
577
|
}
|
|
958
|
-
// Same gap as ty-select had: a plain div's Enter/Space don't turn into
|
|
959
|
-
// click events on their own, so without this a keyboard-only user could
|
|
960
|
-
// Tab to the field but never open it.
|
|
961
578
|
stub.addEventListener('keydown', (e) => {
|
|
962
579
|
const ke = e;
|
|
963
580
|
if (this._state.open)
|
|
@@ -967,10 +584,8 @@ export class TyDatePicker extends TyComponent {
|
|
|
967
584
|
this.handleStubClick(e);
|
|
968
585
|
}
|
|
969
586
|
});
|
|
970
|
-
// Start slot — leading icon (search, calendar variant, etc.)
|
|
971
587
|
const startSlot = document.createElement('slot');
|
|
972
588
|
startSlot.name = 'start';
|
|
973
|
-
// Display text
|
|
974
589
|
const displayText = document.createElement('span');
|
|
975
590
|
displayText.className = 'stub-text';
|
|
976
591
|
const formattedValue = this.getFormattedValue();
|
|
@@ -979,10 +594,8 @@ export class TyDatePicker extends TyComponent {
|
|
|
979
594
|
if (!formattedValue) {
|
|
980
595
|
displayText.classList.add('placeholder');
|
|
981
596
|
}
|
|
982
|
-
// Icons container
|
|
983
597
|
const iconContainer = document.createElement('div');
|
|
984
598
|
iconContainer.className = 'stub-icons';
|
|
985
|
-
// Clear button
|
|
986
599
|
const clearable = this.getProperty('clearable');
|
|
987
600
|
if (clearable && formattedValue && !isDisabled) {
|
|
988
601
|
const clearButton = document.createElement('button');
|
|
@@ -992,23 +605,16 @@ export class TyDatePicker extends TyComponent {
|
|
|
992
605
|
clearButton.addEventListener('click', (e) => this.handleClearClick(e));
|
|
993
606
|
iconContainer.appendChild(clearButton);
|
|
994
607
|
}
|
|
995
|
-
// Calendar icon
|
|
996
608
|
const calendarIcon = document.createElement('span');
|
|
997
609
|
calendarIcon.className = 'stub-arrow';
|
|
998
610
|
calendarIcon.innerHTML = CALENDAR_ICON_SVG;
|
|
999
611
|
iconContainer.appendChild(calendarIcon);
|
|
1000
|
-
// Stub click handler
|
|
1001
612
|
stub.addEventListener('click', (e) => this.handleStubClick(e));
|
|
1002
613
|
stub.appendChild(startSlot);
|
|
1003
614
|
stub.appendChild(displayText);
|
|
1004
615
|
stub.appendChild(iconContainer);
|
|
1005
616
|
return stub;
|
|
1006
617
|
}
|
|
1007
|
-
/**
|
|
1008
|
-
* Create time input element
|
|
1009
|
-
*
|
|
1010
|
-
* PORTED FROM: create-time-input in date_picker.cljs
|
|
1011
|
-
*/
|
|
1012
618
|
createTimeInputElement() {
|
|
1013
619
|
const input = document.createElement('input');
|
|
1014
620
|
input.type = 'text';
|
|
@@ -1018,9 +624,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
1018
624
|
input.maxLength = 5;
|
|
1019
625
|
return input;
|
|
1020
626
|
}
|
|
1021
|
-
/**
|
|
1022
|
-
* Render time input section
|
|
1023
|
-
*/
|
|
1024
627
|
renderTimeSection() {
|
|
1025
628
|
const timeSection = document.createElement('div');
|
|
1026
629
|
timeSection.className = 'time-section';
|
|
@@ -1028,7 +631,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
1028
631
|
timeLabel.className = 'time-label';
|
|
1029
632
|
timeLabel.textContent = 'Time:';
|
|
1030
633
|
const timeInputElement = this.createTimeInputElement();
|
|
1031
|
-
// Create TimeInput instance
|
|
1032
634
|
const hour = this._state.hour || 0;
|
|
1033
635
|
const minute = this._state.minute || 0;
|
|
1034
636
|
this._timeInput = new TimeInput(timeInputElement, this, hour, minute);
|
|
@@ -1040,19 +642,12 @@ export class TyDatePicker extends TyComponent {
|
|
|
1040
642
|
timeSection.appendChild(timeIcon);
|
|
1041
643
|
return timeSection;
|
|
1042
644
|
}
|
|
1043
|
-
/**
|
|
1044
|
-
* Render calendar dropdown with dialog
|
|
1045
|
-
*
|
|
1046
|
-
* PORTED FROM: render-calendar-dropdown in date_picker.cljs
|
|
1047
|
-
*/
|
|
1048
645
|
renderCalendarDropdown() {
|
|
1049
646
|
const dialog = document.createElement('dialog');
|
|
1050
647
|
dialog.className = 'calendar-dialog';
|
|
1051
648
|
const contentWrapper = document.createElement('div');
|
|
1052
649
|
contentWrapper.className = 'calendar-content';
|
|
1053
|
-
// Create ty-calendar
|
|
1054
650
|
const calendar = document.createElement('ty-calendar');
|
|
1055
|
-
// Set current date if available
|
|
1056
651
|
if (this._state.year && this._state.month && this._state.day) {
|
|
1057
652
|
calendar.setAttribute('year', this._state.year.toString());
|
|
1058
653
|
calendar.setAttribute('month', this._state.month.toString());
|
|
@@ -1062,38 +657,26 @@ export class TyDatePicker extends TyComponent {
|
|
|
1062
657
|
if (locale) {
|
|
1063
658
|
calendar.setAttribute('locale', locale);
|
|
1064
659
|
}
|
|
1065
|
-
// Pass through date bounds
|
|
1066
660
|
const min = this.getProperty('min');
|
|
1067
661
|
const max = this.getProperty('max');
|
|
1068
662
|
if (min)
|
|
1069
663
|
calendar.setAttribute('min', min);
|
|
1070
664
|
if (max)
|
|
1071
665
|
calendar.setAttribute('max', max);
|
|
1072
|
-
// Match the popup calendar to the stub's flavor. 'default' is the
|
|
1073
|
-
// stub's own unstyled state, not a real flavor — leave the calendar on
|
|
1074
|
-
// its own default rather than forwarding the literal string.
|
|
1075
666
|
const flavor = this.getProperty('flavor');
|
|
1076
667
|
if (flavor && flavor !== 'default')
|
|
1077
668
|
calendar.setAttribute('flavor', flavor);
|
|
1078
|
-
// Add calendar change handler
|
|
1079
669
|
calendar.addEventListener('change', (e) => this.handleCalendarChange(e));
|
|
1080
670
|
contentWrapper.appendChild(calendar);
|
|
1081
|
-
// Add time section if with-time enabled
|
|
1082
671
|
if (this._state.withTime) {
|
|
1083
672
|
contentWrapper.appendChild(this.renderTimeSection());
|
|
1084
673
|
}
|
|
1085
674
|
dialog.appendChild(contentWrapper);
|
|
1086
|
-
// Dialog close handler
|
|
1087
675
|
dialog.addEventListener('close', () => {
|
|
1088
676
|
this.updateState({ open: false });
|
|
1089
677
|
});
|
|
1090
678
|
return dialog;
|
|
1091
679
|
}
|
|
1092
|
-
/**
|
|
1093
|
-
* Render native date input for mobile touch devices.
|
|
1094
|
-
* Uses <input type="date"> or <input type="datetime-local"> when with-time.
|
|
1095
|
-
* Reuses .date-picker-stub styling and existing event/state infrastructure.
|
|
1096
|
-
*/
|
|
1097
680
|
renderNativeInput() {
|
|
1098
681
|
const wrapper = document.createElement('div');
|
|
1099
682
|
wrapper.className = 'dropdown-wrapper';
|
|
@@ -1103,7 +686,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
1103
686
|
if (isDisabled) {
|
|
1104
687
|
stub.setAttribute('disabled', 'true');
|
|
1105
688
|
}
|
|
1106
|
-
// Native input (hidden, activated via picker indicator)
|
|
1107
689
|
const input = document.createElement('input');
|
|
1108
690
|
input.className = 'native-date-input';
|
|
1109
691
|
input.type = this._state.withTime ? 'datetime-local' : 'date';
|
|
@@ -1111,25 +693,21 @@ export class TyDatePicker extends TyComponent {
|
|
|
1111
693
|
input.disabled = true;
|
|
1112
694
|
if (this.getProperty('required'))
|
|
1113
695
|
input.required = true;
|
|
1114
|
-
// Native min/max (datetime-local wants a time suffix)
|
|
1115
696
|
const minBound = this.getProperty('min');
|
|
1116
697
|
const maxBound = this.getProperty('max');
|
|
1117
698
|
if (minBound)
|
|
1118
699
|
input.min = this._state.withTime ? `${minBound}T00:00` : minBound;
|
|
1119
700
|
if (maxBound)
|
|
1120
701
|
input.max = this._state.withTime ? `${maxBound}T23:59` : maxBound;
|
|
1121
|
-
// Pre-fill value
|
|
1122
702
|
const localValue = componentsToLocalValue(this.getComponents(), this._state.withTime);
|
|
1123
703
|
if (localValue) {
|
|
1124
704
|
input.value = localValue;
|
|
1125
705
|
}
|
|
1126
|
-
// Placeholder span (native date inputs ignore placeholder attr)
|
|
1127
706
|
const placeholder = this.getProperty('placeholder') || 'Select date...';
|
|
1128
707
|
const formattedValue = this.getFormattedValue();
|
|
1129
708
|
const placeholderEl = document.createElement('span');
|
|
1130
709
|
placeholderEl.className = 'stub-text' + (formattedValue ? '' : ' placeholder');
|
|
1131
710
|
placeholderEl.textContent = formattedValue || placeholder;
|
|
1132
|
-
// Change listener
|
|
1133
711
|
input.addEventListener('change', () => {
|
|
1134
712
|
if (!input.value) {
|
|
1135
713
|
this.clearValue();
|
|
@@ -1141,13 +719,11 @@ export class TyDatePicker extends TyComponent {
|
|
|
1141
719
|
this.emitChangeEvent(parsed, 'selection');
|
|
1142
720
|
}
|
|
1143
721
|
});
|
|
1144
|
-
// Start slot — leading icon
|
|
1145
722
|
const startSlot = document.createElement('slot');
|
|
1146
723
|
startSlot.name = 'start';
|
|
1147
724
|
stub.appendChild(startSlot);
|
|
1148
725
|
stub.appendChild(input);
|
|
1149
726
|
stub.appendChild(placeholderEl);
|
|
1150
|
-
// Icons container (clear + calendar icon)
|
|
1151
727
|
const iconContainer = document.createElement('div');
|
|
1152
728
|
iconContainer.className = 'stub-icons';
|
|
1153
729
|
const clearable = this.getProperty('clearable');
|
|
@@ -1167,15 +743,9 @@ export class TyDatePicker extends TyComponent {
|
|
|
1167
743
|
wrapper.appendChild(stub);
|
|
1168
744
|
return wrapper;
|
|
1169
745
|
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Calculate calendar position
|
|
1172
|
-
*
|
|
1173
|
-
* PORTED FROM: calculate-calendar-position! in date_picker.cljs
|
|
1174
|
-
*/
|
|
1175
746
|
calculateCalendarPosition() {
|
|
1176
747
|
if (!this.shadowRoot)
|
|
1177
748
|
return;
|
|
1178
|
-
// Mobile uses native input, no positioning needed
|
|
1179
749
|
if (isMobileTouch())
|
|
1180
750
|
return;
|
|
1181
751
|
const stub = this.shadowRoot.querySelector('.date-picker-stub');
|
|
@@ -1183,26 +753,26 @@ export class TyDatePicker extends TyComponent {
|
|
|
1183
753
|
if (!stub || !dialog)
|
|
1184
754
|
return;
|
|
1185
755
|
const stubRect = stub.getBoundingClientRect();
|
|
1186
|
-
// Measure when shown; the estimate only covers the pre-layout call
|
|
1187
756
|
const dialogRect = dialog.getBoundingClientRect();
|
|
1188
757
|
const estimatedHeight = dialogRect.height || 400;
|
|
1189
758
|
const popupWidth = dialogRect.width || 320;
|
|
1190
759
|
const wrapPadding = 8;
|
|
760
|
+
const anchored = placementToAnchored(this.getProperty('placement'));
|
|
1191
761
|
const pos = computeAnchoredPosition({
|
|
1192
762
|
anchorRect: stubRect,
|
|
1193
763
|
popupWidth,
|
|
1194
764
|
popupHeight: estimatedHeight,
|
|
1195
765
|
gap: 0,
|
|
766
|
+
align: anchored.align,
|
|
767
|
+
side: anchored.side,
|
|
1196
768
|
});
|
|
1197
769
|
const positionBelow = pos.below;
|
|
1198
770
|
const x = pos.x - wrapPadding;
|
|
1199
771
|
const y = positionBelow ? pos.topY : pos.bottomY;
|
|
1200
|
-
// Set CSS variables for positioning
|
|
1201
772
|
this.style.setProperty('--calendar-x', `${x}px`);
|
|
1202
773
|
this.style.setProperty('--calendar-y', `${y}px`);
|
|
1203
774
|
this.style.setProperty('--calendar-offset-x', '0px');
|
|
1204
775
|
this.style.setProperty('--calendar-offset-y', '0px');
|
|
1205
|
-
// Set direction classes
|
|
1206
776
|
if (positionBelow) {
|
|
1207
777
|
dialog.classList.add('position-below');
|
|
1208
778
|
dialog.classList.remove('position-above');
|
|
@@ -1212,11 +782,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
1212
782
|
dialog.classList.remove('position-below');
|
|
1213
783
|
}
|
|
1214
784
|
}
|
|
1215
|
-
/**
|
|
1216
|
-
* Render container structure
|
|
1217
|
-
*
|
|
1218
|
-
* PORTED FROM: render-container-structure in date_picker.cljs
|
|
1219
|
-
*/
|
|
1220
785
|
renderContainer() {
|
|
1221
786
|
const container = document.createElement('div');
|
|
1222
787
|
container.className = 'dropdown-container';
|
|
@@ -1230,40 +795,29 @@ export class TyDatePicker extends TyComponent {
|
|
|
1230
795
|
}
|
|
1231
796
|
return container;
|
|
1232
797
|
}
|
|
1233
|
-
/**
|
|
1234
|
-
* Main render method
|
|
1235
|
-
*
|
|
1236
|
-
* PORTED FROM: render! in date_picker.cljs
|
|
1237
|
-
*/
|
|
1238
798
|
render() {
|
|
1239
799
|
if (!this.shadowRoot)
|
|
1240
800
|
return;
|
|
1241
801
|
ensureStyles(this.shadowRoot, { css: datePickerStyles, id: 'ty-date-picker' });
|
|
1242
|
-
// Mobile: native input, no dialog/event listeners/scroll lock
|
|
1243
802
|
if (isMobileTouch()) {
|
|
1244
803
|
const existingNativeInput = this.shadowRoot.querySelector('.native-date-input');
|
|
1245
804
|
if (existingNativeInput) {
|
|
1246
|
-
// PARTIAL UPDATE: don't destroy DOM while native picker may be open
|
|
1247
805
|
this.updateDisplay();
|
|
1248
806
|
return;
|
|
1249
807
|
}
|
|
1250
|
-
// FIRST RENDER: build from scratch
|
|
1251
808
|
this.shadowRoot.innerHTML = '';
|
|
1252
809
|
const container = this.renderContainer();
|
|
1253
810
|
container.appendChild(this.renderNativeInput());
|
|
1254
811
|
this.shadowRoot.appendChild(container);
|
|
1255
812
|
return;
|
|
1256
813
|
}
|
|
1257
|
-
// Check if dialog is currently open
|
|
1258
814
|
const existingDialog = this.shadowRoot.querySelector('.calendar-dialog');
|
|
1259
815
|
const isDialogOpen = existingDialog && existingDialog.open;
|
|
1260
816
|
if (isDialogOpen && this._state.open) {
|
|
1261
|
-
// PARTIAL UPDATE: Dialog is open - just update display
|
|
1262
817
|
this.updateDisplay();
|
|
1263
818
|
this.calculateCalendarPosition();
|
|
1264
819
|
return;
|
|
1265
820
|
}
|
|
1266
|
-
// FULL REBUILD: Dialog is closed or doesn't exist
|
|
1267
821
|
this.shadowRoot.innerHTML = '';
|
|
1268
822
|
const container = this.renderContainer();
|
|
1269
823
|
const wrapper = document.createElement('div');
|
|
@@ -1272,14 +826,8 @@ export class TyDatePicker extends TyComponent {
|
|
|
1272
826
|
wrapper.appendChild(this.renderCalendarDropdown());
|
|
1273
827
|
container.appendChild(wrapper);
|
|
1274
828
|
this.shadowRoot.appendChild(container);
|
|
1275
|
-
// Setup document-level event listeners
|
|
1276
829
|
this.setupEventListeners();
|
|
1277
830
|
}
|
|
1278
|
-
/**
|
|
1279
|
-
* Update display without destroying DOM (for open dialog)
|
|
1280
|
-
*
|
|
1281
|
-
* PORTED FROM: update-display! in date_picker.cljs
|
|
1282
|
-
*/
|
|
1283
831
|
updateDisplay() {
|
|
1284
832
|
if (!this.shadowRoot)
|
|
1285
833
|
return;
|
|
@@ -1288,18 +836,15 @@ export class TyDatePicker extends TyComponent {
|
|
|
1288
836
|
const hasValue = this.hasDate();
|
|
1289
837
|
const clearable = this.getProperty('clearable');
|
|
1290
838
|
const isDisabled = this.getProperty('disabled');
|
|
1291
|
-
// Update stub text (shared between mobile and desktop)
|
|
1292
839
|
const stubText = this.shadowRoot.querySelector('.stub-text');
|
|
1293
840
|
if (stubText) {
|
|
1294
841
|
stubText.textContent = formattedValue || placeholder;
|
|
1295
842
|
stubText.classList.toggle('placeholder', !formattedValue);
|
|
1296
843
|
}
|
|
1297
|
-
// Update clear button visibility
|
|
1298
844
|
const clearButton = this.shadowRoot.querySelector('.stub-clear');
|
|
1299
845
|
if (clearButton) {
|
|
1300
846
|
clearButton.style.display = (clearable && hasValue && !isDisabled) ? '' : 'none';
|
|
1301
847
|
}
|
|
1302
|
-
// Mobile: also sync native input value
|
|
1303
848
|
if (isMobileTouch()) {
|
|
1304
849
|
const nativeInput = this.shadowRoot.querySelector('.native-date-input');
|
|
1305
850
|
if (nativeInput) {
|
|
@@ -1308,17 +853,12 @@ export class TyDatePicker extends TyComponent {
|
|
|
1308
853
|
}
|
|
1309
854
|
return;
|
|
1310
855
|
}
|
|
1311
|
-
// Desktop: update calendar and time input
|
|
1312
856
|
const calendar = this.shadowRoot.querySelector('ty-calendar');
|
|
1313
857
|
if (calendar && hasValue) {
|
|
1314
858
|
calendar.setAttribute('year', this._state.year.toString());
|
|
1315
859
|
calendar.setAttribute('month', this._state.month.toString());
|
|
1316
860
|
calendar.setAttribute('day', this._state.day.toString());
|
|
1317
861
|
}
|
|
1318
|
-
// Keep an already-open popup's flavor in sync. render() only rebuilds
|
|
1319
|
-
// (and re-forwards flavor) while the dialog is CLOSED — while open it
|
|
1320
|
-
// takes this partial-update path instead, so without this the calendar
|
|
1321
|
-
// would stay stuck on whatever flavor was active when it was opened.
|
|
1322
862
|
if (calendar) {
|
|
1323
863
|
const flavor = this.getProperty('flavor');
|
|
1324
864
|
if (flavor && flavor !== 'default')
|
|
@@ -1330,55 +870,35 @@ export class TyDatePicker extends TyComponent {
|
|
|
1330
870
|
this._timeInput.setTime(this._state.hour, this._state.minute);
|
|
1331
871
|
}
|
|
1332
872
|
}
|
|
1333
|
-
/**
|
|
1334
|
-
* Setup document-level event listeners
|
|
1335
|
-
*/
|
|
1336
873
|
setupEventListeners() {
|
|
1337
|
-
// Remove old listeners if they exist
|
|
1338
874
|
if (this._clickListener) {
|
|
1339
875
|
document.removeEventListener('click', this._clickListener);
|
|
1340
876
|
}
|
|
1341
877
|
if (this._keydownListener) {
|
|
1342
878
|
document.removeEventListener('keydown', this._keydownListener);
|
|
1343
879
|
}
|
|
1344
|
-
// Create new listeners
|
|
1345
880
|
this._clickListener = (e) => this.handleOutsideClick(e);
|
|
1346
881
|
this._keydownListener = (e) => this.handleEscapeKey(e);
|
|
1347
|
-
// Add document listeners
|
|
1348
882
|
document.addEventListener('click', this._clickListener);
|
|
1349
883
|
document.addEventListener('keydown', this._keydownListener);
|
|
1350
|
-
// Add dialog click listener
|
|
1351
884
|
const dialog = this.shadowRoot?.querySelector('.calendar-dialog');
|
|
1352
885
|
if (dialog) {
|
|
1353
886
|
this._dialogClickListener = (e) => this.handleDialogClick(e);
|
|
1354
887
|
dialog.addEventListener('click', this._dialogClickListener);
|
|
1355
888
|
}
|
|
1356
889
|
}
|
|
1357
|
-
// ==========================================================================
|
|
1358
|
-
// Event Handlers
|
|
1359
|
-
// ==========================================================================
|
|
1360
890
|
handleStubClick(event) {
|
|
1361
891
|
event.preventDefault();
|
|
1362
|
-
// Heal state/DOM desync: a host re-render can replace the dialog element
|
|
1363
|
-
// while `open` is still true, stranding the picker (openDropdown
|
|
1364
|
-
// early-returns on _state.open). Reality wins: closed dialog → closed state.
|
|
1365
892
|
const dialog = this.shadowRoot?.querySelector('.calendar-dialog');
|
|
1366
893
|
if (this._state.open && (!dialog || !dialog.open)) {
|
|
1367
894
|
this.updateState({ open: false }, true);
|
|
1368
895
|
unlockScroll(`date-picker-${this.id || this.toString()}`);
|
|
1369
896
|
}
|
|
1370
897
|
const disabled = this.getProperty('disabled');
|
|
1371
|
-
// FIXME: Timestamp guard is a workaround. Calendar day cells use pointerdown,
|
|
1372
|
-
// which closes the dialog before the click event fires — the click then hits the
|
|
1373
|
-
// stub and reopens. Proper fix: switch calendar-month day cells from pointerdown
|
|
1374
|
-
// to click so the event is consumed before the dialog closes.
|
|
1375
898
|
if (!disabled && Date.now() - this._closeTimestamp > 300) {
|
|
1376
899
|
this.openDropdown();
|
|
1377
900
|
}
|
|
1378
901
|
}
|
|
1379
|
-
/**
|
|
1380
|
-
* Clear the date value, sync state, emit event, and re-render.
|
|
1381
|
-
*/
|
|
1382
902
|
clearValue() {
|
|
1383
903
|
this.updateState({
|
|
1384
904
|
year: undefined,
|
|
@@ -1396,11 +916,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
1396
916
|
event.stopPropagation();
|
|
1397
917
|
this.clearValue();
|
|
1398
918
|
}
|
|
1399
|
-
/**
|
|
1400
|
-
* Handle calendar date selection
|
|
1401
|
-
*
|
|
1402
|
-
* PORTED FROM: handle-calendar-change! in date_picker.cljs
|
|
1403
|
-
*/
|
|
1404
919
|
handleCalendarChange(event) {
|
|
1405
920
|
event.preventDefault();
|
|
1406
921
|
event.stopPropagation();
|
|
@@ -1409,7 +924,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
1409
924
|
const dayContext = detail.dayContext;
|
|
1410
925
|
if (!dayContext)
|
|
1411
926
|
return;
|
|
1412
|
-
// Update state with new date
|
|
1413
927
|
const newComponents = {
|
|
1414
928
|
year: dayContext.year,
|
|
1415
929
|
month: dayContext.month,
|
|
@@ -1417,15 +931,12 @@ export class TyDatePicker extends TyComponent {
|
|
|
1417
931
|
hour: this._state.hour,
|
|
1418
932
|
minute: this._state.minute,
|
|
1419
933
|
};
|
|
1420
|
-
// Force sync when calendar date changes
|
|
1421
934
|
this.updateState(newComponents, true);
|
|
1422
935
|
this.emitChangeEvent(newComponents, 'selection');
|
|
1423
|
-
// Close calendar if time is not required
|
|
1424
936
|
if (!this._state.withTime) {
|
|
1425
937
|
this.closeDropdown();
|
|
1426
938
|
}
|
|
1427
939
|
else {
|
|
1428
|
-
// Auto-focus time input after date selection
|
|
1429
940
|
requestAnimationFrame(() => {
|
|
1430
941
|
if (!this.shadowRoot)
|
|
1431
942
|
return;
|
|
@@ -1436,43 +947,26 @@ export class TyDatePicker extends TyComponent {
|
|
|
1436
947
|
});
|
|
1437
948
|
}
|
|
1438
949
|
}
|
|
1439
|
-
/**
|
|
1440
|
-
* Handle dialog backdrop clicks
|
|
1441
|
-
*
|
|
1442
|
-
* PORTED FROM: handle-dialog-click! in date_picker.cljs
|
|
1443
|
-
*/
|
|
1444
950
|
handleDialogClick(event) {
|
|
1445
951
|
if (!this.shadowRoot)
|
|
1446
952
|
return;
|
|
1447
953
|
const dialog = this.shadowRoot.querySelector('.calendar-dialog');
|
|
1448
954
|
const content = this.shadowRoot.querySelector('.calendar-content');
|
|
1449
|
-
// Close if clicking on dialog backdrop (not calendar content)
|
|
1450
955
|
if (event.target === dialog && this._state.open && content && !content.contains(event.target)) {
|
|
1451
956
|
event.preventDefault();
|
|
1452
957
|
event.stopPropagation();
|
|
1453
958
|
this.closeDropdown();
|
|
1454
959
|
}
|
|
1455
960
|
}
|
|
1456
|
-
/**
|
|
1457
|
-
* Handle clicks outside the date picker
|
|
1458
|
-
*
|
|
1459
|
-
* PORTED FROM: handle-outside-click! in date_picker.cljs
|
|
1460
|
-
*/
|
|
1461
961
|
handleOutsideClick(event) {
|
|
1462
962
|
if (!this.shadowRoot)
|
|
1463
963
|
return;
|
|
1464
964
|
const target = event.target;
|
|
1465
965
|
const dialog = this.shadowRoot.querySelector('.calendar-dialog');
|
|
1466
|
-
// Check if click is truly outside everything
|
|
1467
966
|
if (this._state.open && dialog && !this.contains(target) && !dialog.contains(target)) {
|
|
1468
967
|
this.closeDropdown();
|
|
1469
968
|
}
|
|
1470
969
|
}
|
|
1471
|
-
/**
|
|
1472
|
-
* Handle Escape key press
|
|
1473
|
-
*
|
|
1474
|
-
* PORTED FROM: handle-escape-key! in date_picker.cljs
|
|
1475
|
-
*/
|
|
1476
970
|
handleEscapeKey(event) {
|
|
1477
971
|
const keyboardEvent = event;
|
|
1478
972
|
if (keyboardEvent.key === 'Escape' && this._state.open) {
|
|
@@ -1480,22 +974,13 @@ export class TyDatePicker extends TyComponent {
|
|
|
1480
974
|
this.closeDropdown();
|
|
1481
975
|
}
|
|
1482
976
|
}
|
|
1483
|
-
/**
|
|
1484
|
-
* Open calendar dropdown
|
|
1485
|
-
*
|
|
1486
|
-
* PORTED FROM: open-dropdown! in date_picker.cljs
|
|
1487
|
-
*/
|
|
1488
977
|
openDropdown() {
|
|
1489
978
|
if (this._state.open)
|
|
1490
979
|
return;
|
|
1491
980
|
if (isMobileTouch())
|
|
1492
981
|
return;
|
|
1493
982
|
this.updateState({ open: true });
|
|
1494
|
-
|
|
1495
|
-
this.dispatchEvent(new CustomEvent('open', {
|
|
1496
|
-
bubbles: true,
|
|
1497
|
-
composed: true
|
|
1498
|
-
}));
|
|
983
|
+
this.dispatchEvent(new CustomEvent('open'));
|
|
1499
984
|
this.render();
|
|
1500
985
|
requestAnimationFrame(() => {
|
|
1501
986
|
if (!this.shadowRoot)
|
|
@@ -1510,7 +995,6 @@ export class TyDatePicker extends TyComponent {
|
|
|
1510
995
|
const stubEl = this.shadowRoot.querySelector('.date-picker-stub');
|
|
1511
996
|
if (stubEl)
|
|
1512
997
|
stubEl.setAttribute('aria-expanded', 'true');
|
|
1513
|
-
// Remove focus from any focused elements to prevent the blue outline
|
|
1514
998
|
const focusedElement = this.shadowRoot.activeElement;
|
|
1515
999
|
if (focusedElement) {
|
|
1516
1000
|
focusedElement.blur();
|
|
@@ -1518,17 +1002,11 @@ export class TyDatePicker extends TyComponent {
|
|
|
1518
1002
|
}
|
|
1519
1003
|
});
|
|
1520
1004
|
}
|
|
1521
|
-
/**
|
|
1522
|
-
* Close calendar dropdown
|
|
1523
|
-
*
|
|
1524
|
-
* PORTED FROM: close-dropdown! in date_picker.cljs
|
|
1525
|
-
*/
|
|
1526
1005
|
closeDropdown() {
|
|
1527
1006
|
if (!this._state.open)
|
|
1528
1007
|
return;
|
|
1529
1008
|
this._closeTimestamp = Date.now();
|
|
1530
1009
|
const pickerId = `date-picker-${this.id || this.toString()}`;
|
|
1531
|
-
// Force sync any staged updates when closing
|
|
1532
1010
|
this.updateState({ open: false }, true);
|
|
1533
1011
|
unlockScroll(pickerId);
|
|
1534
1012
|
if (this.shadowRoot) {
|
|
@@ -1537,63 +1015,46 @@ export class TyDatePicker extends TyComponent {
|
|
|
1537
1015
|
dialog.classList.remove('position-above', 'position-below', 'open');
|
|
1538
1016
|
dialog.close();
|
|
1539
1017
|
}
|
|
1540
|
-
// closeDropdown() doesn't call render(), so the stub's aria-expanded
|
|
1541
|
-
// (still "true" from open) needs an explicit flip here too.
|
|
1542
1018
|
const stubEl = this.shadowRoot.querySelector('.date-picker-stub');
|
|
1543
1019
|
if (stubEl)
|
|
1544
1020
|
stubEl.setAttribute('aria-expanded', 'false');
|
|
1545
1021
|
}
|
|
1546
|
-
this.dispatchEvent(new CustomEvent('close'
|
|
1547
|
-
bubbles: true,
|
|
1548
|
-
composed: true
|
|
1549
|
-
}));
|
|
1022
|
+
this.dispatchEvent(new CustomEvent('close'));
|
|
1550
1023
|
this.render();
|
|
1551
1024
|
}
|
|
1552
|
-
// ==========================================================================
|
|
1553
|
-
// Cleanup
|
|
1554
|
-
// ==========================================================================
|
|
1555
|
-
/**
|
|
1556
|
-
* Clean up event listeners and state
|
|
1557
|
-
*
|
|
1558
|
-
* PORTED FROM: cleanup! in date_picker.cljs
|
|
1559
|
-
*/
|
|
1560
1025
|
cleanup() {
|
|
1561
|
-
// Remove document listeners
|
|
1562
1026
|
if (this._clickListener) {
|
|
1563
1027
|
document.removeEventListener('click', this._clickListener);
|
|
1564
1028
|
}
|
|
1565
1029
|
if (this._keydownListener) {
|
|
1566
1030
|
document.removeEventListener('keydown', this._keydownListener);
|
|
1567
1031
|
}
|
|
1568
|
-
// Remove dialog listener
|
|
1569
1032
|
if (this._dialogClickListener && this.shadowRoot) {
|
|
1570
1033
|
const dialog = this.shadowRoot.querySelector('.calendar-dialog');
|
|
1571
1034
|
if (dialog) {
|
|
1572
1035
|
dialog.removeEventListener('click', this._dialogClickListener);
|
|
1573
1036
|
}
|
|
1574
1037
|
}
|
|
1575
|
-
// Unlock scroll if open
|
|
1576
1038
|
if (this._state.open) {
|
|
1577
1039
|
const pickerId = `date-picker-${this.id || this.toString()}`;
|
|
1578
1040
|
unlockScroll(pickerId);
|
|
1579
1041
|
}
|
|
1580
|
-
// Clear references
|
|
1581
1042
|
this._clickListener = undefined;
|
|
1582
1043
|
this._keydownListener = undefined;
|
|
1583
1044
|
this._dialogClickListener = undefined;
|
|
1584
1045
|
this._timeInput = undefined;
|
|
1585
1046
|
}
|
|
1586
1047
|
}
|
|
1587
|
-
// ============================================================================
|
|
1588
|
-
// PROPERTY CONFIGURATION - Single source of truth
|
|
1589
|
-
// ============================================================================
|
|
1590
1048
|
TyDatePicker.properties = {
|
|
1591
|
-
|
|
1049
|
+
placement: {
|
|
1050
|
+
type: 'string',
|
|
1051
|
+
visual: true,
|
|
1052
|
+
default: '',
|
|
1053
|
+
},
|
|
1592
1054
|
size: {
|
|
1593
1055
|
type: 'string',
|
|
1594
1056
|
visual: true,
|
|
1595
1057
|
default: 'md',
|
|
1596
|
-
// Fields come in exactly three sizes; legacy xs/xl map to sm/lg.
|
|
1597
1058
|
validate: (v) => ['sm', 'md', 'lg'].includes(v),
|
|
1598
1059
|
coerce: (v) => {
|
|
1599
1060
|
if (v === 'xs')
|
|
@@ -1624,10 +1085,9 @@ TyDatePicker.properties = {
|
|
|
1624
1085
|
},
|
|
1625
1086
|
name: {
|
|
1626
1087
|
type: 'string',
|
|
1627
|
-
visual: false,
|
|
1088
|
+
visual: false,
|
|
1628
1089
|
default: ''
|
|
1629
1090
|
},
|
|
1630
|
-
// Date bounds (ISO) — passed through to the embedded ty-calendar
|
|
1631
1091
|
min: {
|
|
1632
1092
|
type: 'string',
|
|
1633
1093
|
visual: true,
|
|
@@ -1638,7 +1098,6 @@ TyDatePicker.properties = {
|
|
|
1638
1098
|
visual: true,
|
|
1639
1099
|
default: ''
|
|
1640
1100
|
},
|
|
1641
|
-
// Boolean properties
|
|
1642
1101
|
required: {
|
|
1643
1102
|
type: 'boolean',
|
|
1644
1103
|
visual: true,
|
|
@@ -1657,7 +1116,6 @@ TyDatePicker.properties = {
|
|
|
1657
1116
|
'not-clearable': false
|
|
1658
1117
|
}
|
|
1659
1118
|
},
|
|
1660
|
-
// Format property (medium complexity)
|
|
1661
1119
|
format: {
|
|
1662
1120
|
type: 'string',
|
|
1663
1121
|
visual: true,
|
|
@@ -1672,34 +1130,25 @@ TyDatePicker.properties = {
|
|
|
1672
1130
|
return v;
|
|
1673
1131
|
}
|
|
1674
1132
|
},
|
|
1675
|
-
// Locale property (medium complexity - has observer)
|
|
1676
1133
|
locale: {
|
|
1677
1134
|
type: 'string',
|
|
1678
1135
|
visual: true,
|
|
1679
1136
|
default: ''
|
|
1680
1137
|
},
|
|
1681
|
-
// Value property (high complexity - UTC parsing, date components)
|
|
1682
|
-
// Note: Custom getter/setter will handle the complex logic
|
|
1683
1138
|
value: {
|
|
1684
1139
|
type: 'string',
|
|
1685
1140
|
visual: true,
|
|
1686
|
-
formValue: true,
|
|
1687
|
-
emitChange: false,
|
|
1688
|
-
default: null
|
|
1141
|
+
formValue: true,
|
|
1142
|
+
emitChange: false,
|
|
1143
|
+
default: null
|
|
1689
1144
|
},
|
|
1690
|
-
// With-time property (high complexity - affects parsing and rendering)
|
|
1691
1145
|
'with-time': {
|
|
1692
1146
|
type: 'boolean',
|
|
1693
1147
|
visual: true,
|
|
1694
1148
|
default: false
|
|
1695
1149
|
}
|
|
1696
1150
|
};
|
|
1697
|
-
/**
|
|
1698
|
-
* Form-associated custom element
|
|
1699
|
-
*/
|
|
1700
1151
|
TyDatePicker.formAssociated = true;
|
|
1701
|
-
// Register custom element
|
|
1702
1152
|
if (!customElements.get('ty-date-picker')) {
|
|
1703
1153
|
customElements.define('ty-date-picker', TyDatePicker);
|
|
1704
1154
|
}
|
|
1705
|
-
//# sourceMappingURL=date-picker.js.map
|