@livenetworks/ashlar 1.3.2
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 +177 -0
- package/js/COMPONENTS.md +1102 -0
- package/js/index.js +41 -0
- package/js/ln-accordion/README.md +137 -0
- package/js/ln-accordion/ln-accordion.js +1 -0
- package/js/ln-accordion/src/ln-accordion.js +41 -0
- package/js/ln-ajax/README.md +91 -0
- package/js/ln-ajax/ln-ajax.js +1 -0
- package/js/ln-ajax/src/ln-ajax.js +277 -0
- package/js/ln-api-connector/README.md +150 -0
- package/js/ln-api-connector/ln-api-connector.js +1 -0
- package/js/ln-api-connector/src/ln-api-connector.js +265 -0
- package/js/ln-autoresize/README.md +80 -0
- package/js/ln-autoresize/ln-autoresize.js +1 -0
- package/js/ln-autoresize/src/ln-autoresize.js +47 -0
- package/js/ln-autosave/README.md +92 -0
- package/js/ln-autosave/ln-autosave.js +1 -0
- package/js/ln-autosave/src/ln-autosave.js +147 -0
- package/js/ln-circular-progress/README.md +161 -0
- package/js/ln-circular-progress/ln-circular-progress.js +1 -0
- package/js/ln-circular-progress/src/ln-circular-progress.js +133 -0
- package/js/ln-confirm/README.md +86 -0
- package/js/ln-confirm/_ln-confirm.scss +13 -0
- package/js/ln-confirm/ln-confirm.js +1 -0
- package/js/ln-confirm/src/ln-confirm.js +131 -0
- package/js/ln-core/crypto.js +83 -0
- package/js/ln-core/helpers.js +411 -0
- package/js/ln-core/index.js +5 -0
- package/js/ln-core/persist.js +71 -0
- package/js/ln-core/positioning.js +207 -0
- package/js/ln-core/reactive.js +74 -0
- package/js/ln-couchdb-connector/README.md +156 -0
- package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -0
- package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +348 -0
- package/js/ln-data-coordinator/README.md +165 -0
- package/js/ln-data-coordinator/ln-data-coordinator.js +1 -0
- package/js/ln-data-coordinator/src/ln-data-coordinator.js +249 -0
- package/js/ln-data-store/README.md +94 -0
- package/js/ln-data-store/ln-data-store.js +1 -0
- package/js/ln-data-store/src/ln-data-store.js +699 -0
- package/js/ln-data-table/README.md +110 -0
- package/js/ln-data-table/ln-data-table.js +1 -0
- package/js/ln-data-table/ln-data-table.scss +10 -0
- package/js/ln-data-table/src/ln-data-table.js +1103 -0
- package/js/ln-date/README.md +151 -0
- package/js/ln-date/ln-date.js +1 -0
- package/js/ln-date/src/ln-date.js +442 -0
- package/js/ln-dropdown/README.md +117 -0
- package/js/ln-dropdown/ln-dropdown.js +1 -0
- package/js/ln-dropdown/ln-dropdown.scss +15 -0
- package/js/ln-dropdown/src/ln-dropdown.js +174 -0
- package/js/ln-external-links/README.md +341 -0
- package/js/ln-external-links/ln-external-links.js +1 -0
- package/js/ln-external-links/src/ln-external-links.js +116 -0
- package/js/ln-filter/README.md +99 -0
- package/js/ln-filter/ln-filter.js +1 -0
- package/js/ln-filter/ln-filter.scss +7 -0
- package/js/ln-filter/src/ln-filter.js +404 -0
- package/js/ln-form/README.md +101 -0
- package/js/ln-form/ln-form.js +1 -0
- package/js/ln-form/src/ln-form.js +199 -0
- package/js/ln-http/README.md +89 -0
- package/js/ln-http/ln-http.js +1 -0
- package/js/ln-http/src/ln-http.js +219 -0
- package/js/ln-icons/README.md +88 -0
- package/js/ln-icons/ln-icons.js +1 -0
- package/js/ln-icons/src/ln-icons.js +169 -0
- package/js/ln-link/README.md +303 -0
- package/js/ln-link/ln-link.js +1 -0
- package/js/ln-link/src/ln-link.js +196 -0
- package/js/ln-modal/README.md +154 -0
- package/js/ln-modal/ln-modal.js +1 -0
- package/js/ln-modal/ln-modal.scss +11 -0
- package/js/ln-modal/src/ln-modal.js +201 -0
- package/js/ln-nav/README.md +70 -0
- package/js/ln-nav/ln-nav.js +1 -0
- package/js/ln-nav/src/ln-nav.js +177 -0
- package/js/ln-number/README.md +122 -0
- package/js/ln-number/ln-number.js +1 -0
- package/js/ln-number/src/ln-number.js +302 -0
- package/js/ln-popover/README.md +127 -0
- package/js/ln-popover/ln-popover.js +1 -0
- package/js/ln-popover/src/ln-popover.js +288 -0
- package/js/ln-progress/README.md +442 -0
- package/js/ln-progress/ln-progress.js +1 -0
- package/js/ln-progress/src/ln-progress.js +150 -0
- package/js/ln-search/README.md +83 -0
- package/js/ln-search/ln-search.js +1 -0
- package/js/ln-search/ln-search.scss +7 -0
- package/js/ln-search/src/ln-search.js +114 -0
- package/js/ln-sortable/README.md +95 -0
- package/js/ln-sortable/ln-sortable.js +1 -0
- package/js/ln-sortable/src/ln-sortable.js +203 -0
- package/js/ln-table/README.md +101 -0
- package/js/ln-table/ln-table-sort.js +1 -0
- package/js/ln-table/ln-table.js +1 -0
- package/js/ln-table/ln-table.scss +11 -0
- package/js/ln-table/src/ln-table-sort.js +168 -0
- package/js/ln-table/src/ln-table.js +473 -0
- package/js/ln-tabs/README.md +137 -0
- package/js/ln-tabs/ln-tabs.js +1 -0
- package/js/ln-tabs/src/ln-tabs.js +171 -0
- package/js/ln-time/README.md +81 -0
- package/js/ln-time/ln-time.js +1 -0
- package/js/ln-time/src/ln-time.js +192 -0
- package/js/ln-toast/README.md +122 -0
- package/js/ln-toast/ln-toast.js +15 -0
- package/js/ln-toast/src/ln-toast.js +210 -0
- package/js/ln-toast/template.html +14 -0
- package/js/ln-toggle/README.md +137 -0
- package/js/ln-toggle/ln-toggle.js +1 -0
- package/js/ln-toggle/src/ln-toggle.js +139 -0
- package/js/ln-tooltip/README.md +58 -0
- package/js/ln-tooltip/ln-tooltip.js +1 -0
- package/js/ln-tooltip/ln-tooltip.scss +9 -0
- package/js/ln-tooltip/src/ln-tooltip.js +169 -0
- package/js/ln-translations/README.md +96 -0
- package/js/ln-translations/ln-translations.js +1 -0
- package/js/ln-translations/src/ln-translations.js +275 -0
- package/js/ln-upload/README.md +180 -0
- package/js/ln-upload/ln-upload.js +1 -0
- package/js/ln-upload/ln-upload.scss +20 -0
- package/js/ln-upload/src/ln-upload.js +407 -0
- package/js/ln-validate/README.md +108 -0
- package/js/ln-validate/ln-validate.js +1 -0
- package/js/ln-validate/src/ln-validate.js +160 -0
- package/package.json +55 -0
- package/scss/base/_global.scss +83 -0
- package/scss/base/_reset.scss +17 -0
- package/scss/base/_typography.scss +125 -0
- package/scss/components/_accordion.scss +34 -0
- package/scss/components/_ajax.scss +15 -0
- package/scss/components/_alert.scss +5 -0
- package/scss/components/_app-shell.scss +15 -0
- package/scss/components/_avatar.scss +6 -0
- package/scss/components/_breadcrumbs.scss +33 -0
- package/scss/components/_button.scss +20 -0
- package/scss/components/_card.scss +10 -0
- package/scss/components/_chip.scss +5 -0
- package/scss/components/_circular-progress.scss +29 -0
- package/scss/components/_confirm.scss +5 -0
- package/scss/components/_data-table.scss +83 -0
- package/scss/components/_dropdown.scss +25 -0
- package/scss/components/_empty-state.scss +22 -0
- package/scss/components/_form.scss +100 -0
- package/scss/components/_layout.scss +8 -0
- package/scss/components/_link.scss +11 -0
- package/scss/components/_ln-table.scss +60 -0
- package/scss/components/_loader.scss +6 -0
- package/scss/components/_modal.scss +20 -0
- package/scss/components/_nav.scss +9 -0
- package/scss/components/_page-header.scss +10 -0
- package/scss/components/_popover.scss +10 -0
- package/scss/components/_progress.scss +17 -0
- package/scss/components/_prose.scss +5 -0
- package/scss/components/_scrollbar.scss +32 -0
- package/scss/components/_sections.scss +12 -0
- package/scss/components/_sidebar.scss +5 -0
- package/scss/components/_stat-card.scss +5 -0
- package/scss/components/_status-badge.scss +4 -0
- package/scss/components/_stepper.scss +5 -0
- package/scss/components/_table.scss +19 -0
- package/scss/components/_tabs.scss +21 -0
- package/scss/components/_timeline.scss +14 -0
- package/scss/components/_toast.scss +41 -0
- package/scss/components/_toggle.scss +81 -0
- package/scss/components/_tooltip.scss +18 -0
- package/scss/components/_translations.scss +111 -0
- package/scss/components/_upload.scss +51 -0
- package/scss/config/_breakpoints.scss +72 -0
- package/scss/config/_density.scss +117 -0
- package/scss/config/_icons.scss +37 -0
- package/scss/config/_mixins.scss +13 -0
- package/scss/config/_theme.scss +216 -0
- package/scss/config/_tokens.scss +419 -0
- package/scss/config/mixins/_accordion.scss +52 -0
- package/scss/config/mixins/_ajax.scss +39 -0
- package/scss/config/mixins/_alert.scss +82 -0
- package/scss/config/mixins/_app-shell.scss +312 -0
- package/scss/config/mixins/_avatar.scss +109 -0
- package/scss/config/mixins/_borders.scss +36 -0
- package/scss/config/mixins/_breadcrumbs.scss +72 -0
- package/scss/config/mixins/_breakpoints.scss +62 -0
- package/scss/config/mixins/_btn.scss +179 -0
- package/scss/config/mixins/_card.scss +338 -0
- package/scss/config/mixins/_chip.scss +66 -0
- package/scss/config/mixins/_circular-progress.scss +71 -0
- package/scss/config/mixins/_collapsible.scss +24 -0
- package/scss/config/mixins/_colors.scss +46 -0
- package/scss/config/mixins/_confirm.scss +31 -0
- package/scss/config/mixins/_data-table.scss +346 -0
- package/scss/config/mixins/_display.scss +32 -0
- package/scss/config/mixins/_dropdown.scss +143 -0
- package/scss/config/mixins/_empty-state.scss +30 -0
- package/scss/config/mixins/_focus.scss +55 -0
- package/scss/config/mixins/_footer.scss +42 -0
- package/scss/config/mixins/_form.scss +601 -0
- package/scss/config/mixins/_index.scss +58 -0
- package/scss/config/mixins/_interaction.scss +15 -0
- package/scss/config/mixins/_kbd.scss +22 -0
- package/scss/config/mixins/_layout.scss +117 -0
- package/scss/config/mixins/_link.scss +55 -0
- package/scss/config/mixins/_ln-table.scss +420 -0
- package/scss/config/mixins/_loader.scss +26 -0
- package/scss/config/mixins/_modal.scss +66 -0
- package/scss/config/mixins/_motion.scss +19 -0
- package/scss/config/mixins/_nav.scss +273 -0
- package/scss/config/mixins/_page-header.scss +69 -0
- package/scss/config/mixins/_popover.scss +25 -0
- package/scss/config/mixins/_position.scss +32 -0
- package/scss/config/mixins/_progress.scss +56 -0
- package/scss/config/mixins/_prose.scss +127 -0
- package/scss/config/mixins/_shadows.scss +8 -0
- package/scss/config/mixins/_sidebar.scss +95 -0
- package/scss/config/mixins/_sizing.scss +6 -0
- package/scss/config/mixins/_spacing.scss +19 -0
- package/scss/config/mixins/_stat-card.scss +68 -0
- package/scss/config/mixins/_status-badge.scss +83 -0
- package/scss/config/mixins/_stepper.scss +78 -0
- package/scss/config/mixins/_table.scss +215 -0
- package/scss/config/mixins/_tabs.scss +64 -0
- package/scss/config/mixins/_timeline.scss +69 -0
- package/scss/config/mixins/_toast.scss +148 -0
- package/scss/config/mixins/_tooltip.scss +111 -0
- package/scss/config/mixins/_transitions.scss +10 -0
- package/scss/config/mixins/_translations.scss +124 -0
- package/scss/config/mixins/_typography.scss +57 -0
- package/scss/config/mixins/_upload.scss +168 -0
- package/scss/ln-ashlar.scss +62 -0
- package/scss/tabler-icons.txt +5039 -0
- package/scss/utilities/_animations.scss +83 -0
- package/scss/utilities/_utilities.scss +49 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# ln-date
|
|
2
|
+
|
|
3
|
+
Locale-aware date formatting with native browser picker.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<input type="date" name="birthday" data-ln-date>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
On initialization, the component transforms the input into a text-display element with a paired calendar button and a hidden form-submit input. See [`docs/js/date.md`](../../docs/js/date.md) for the full transform.
|
|
12
|
+
|
|
13
|
+
## Typing Support
|
|
14
|
+
|
|
15
|
+
The visible input accepts typed dates. On blur, the component parses the
|
|
16
|
+
input and reformats it according to the configured display format.
|
|
17
|
+
|
|
18
|
+
### Supported Input Formats
|
|
19
|
+
|
|
20
|
+
| Separator | Assumed Format | Example |
|
|
21
|
+
|-----------|---------------|---------|
|
|
22
|
+
| `.` (dot) | dd.MM.yyyy (European) | `30.12.1979` or `30.12.79` |
|
|
23
|
+
| `/` (slash) | MM/dd/yyyy (US) | `12/30/1979` or `12/30/79` |
|
|
24
|
+
| `-` (dash) | yyyy-MM-dd if 4-digit first part (ISO), else dd-MM-yyyy | `1979-12-30` or `30-12-1979` |
|
|
25
|
+
|
|
26
|
+
Two-digit years: 00–49 → 2000–2049, 50–99 → 1950–1999.
|
|
27
|
+
|
|
28
|
+
### Blur Behavior
|
|
29
|
+
|
|
30
|
+
- **Empty input**: clears the date value and dispatches `ln-date:change`
|
|
31
|
+
- **Unchanged text**: no action (user clicked in and out without editing)
|
|
32
|
+
- **Valid date**: updates the value, reformats display, dispatches `ln-date:change`
|
|
33
|
+
- **Invalid input**: reverts to the previous formatted display
|
|
34
|
+
|
|
35
|
+
The hidden input holds the ISO date string (YYYY-MM-DD) for form submission.
|
|
36
|
+
|
|
37
|
+
## Attributes
|
|
38
|
+
|
|
39
|
+
| Attribute | On | Description |
|
|
40
|
+
|-----------|-----|-------------|
|
|
41
|
+
| `data-ln-date` | `<input>` | Enables date formatting. Value = format (keyword or pattern). Default: `medium` |
|
|
42
|
+
|
|
43
|
+
### Format Keywords (locale-aware via Intl.DateTimeFormat)
|
|
44
|
+
|
|
45
|
+
| Value | Example (mk) | Example (en) |
|
|
46
|
+
|-------|-------------|-------------|
|
|
47
|
+
| (empty) | 19 апр 2026 | Apr 19, 2026 |
|
|
48
|
+
| `short` | 19.4.2026 | 4/19/2026 |
|
|
49
|
+
| `medium` | 19 апр 2026 | Apr 19, 2026 |
|
|
50
|
+
| `medium datetime` | 19 апр 2026, 14:30 | Apr 19, 2026, 2:30 PM |
|
|
51
|
+
| `long` | 19 април 2026 | April 19, 2026 |
|
|
52
|
+
| `short datetime` | 19.4.2026 14:30 | 4/19/26, 2:30 PM |
|
|
53
|
+
| `long datetime` | 19 април 2026, 14:30 | April 19, 2026, 2:30 PM |
|
|
54
|
+
|
|
55
|
+
### Custom Pattern Tokens
|
|
56
|
+
|
|
57
|
+
| Token | Meaning | Example |
|
|
58
|
+
|-------|---------|---------|
|
|
59
|
+
| `dd` | Day 2-digit | 05 |
|
|
60
|
+
| `d` | Day | 5 |
|
|
61
|
+
| `MM` | Month 2-digit | 04 |
|
|
62
|
+
| `M` | Month | 4 |
|
|
63
|
+
| `MMM` | Month short name (locale) | апр |
|
|
64
|
+
| `MMMM` | Month full name (locale) | април |
|
|
65
|
+
| `yyyy` | Year 4-digit | 2026 |
|
|
66
|
+
| `yy` | Year 2-digit | 26 |
|
|
67
|
+
| `HH` | Hours 24h | 14 |
|
|
68
|
+
| `mm` | Minutes | 30 |
|
|
69
|
+
|
|
70
|
+
## Events
|
|
71
|
+
|
|
72
|
+
| Event | Bubbles | Cancelable | Detail |
|
|
73
|
+
|-------|---------|------------|--------|
|
|
74
|
+
| `ln-date:change` | yes | no | `{ value: String (ISO), formatted: String, date: Date }` |
|
|
75
|
+
| `ln-date:destroyed` | yes | no | `{ target: Element }` |
|
|
76
|
+
|
|
77
|
+
## API
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
const el = document.querySelector('[data-ln-date]');
|
|
81
|
+
|
|
82
|
+
el.lnDate.value; // get ISO date string (YYYY-MM-DD) or empty string
|
|
83
|
+
el.lnDate.value = '2026-04-19'; // set value programmatically — formats display
|
|
84
|
+
el.lnDate.date; // get Date object or null
|
|
85
|
+
el.lnDate.date = new Date(); // set via Date object
|
|
86
|
+
el.lnDate.formatted; // get formatted display string
|
|
87
|
+
|
|
88
|
+
el.lnDate.destroy(); // remove component, restore original input
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Locale Detection
|
|
92
|
+
|
|
93
|
+
Locale is detected by walking up the DOM tree to find the nearest `[lang]` attribute (e.g. `<html lang="mk">` → `mk`, `<html lang="en-US">` → `en-US`). Fallback: `navigator.language`. Changes to the `lang` attribute on `<html>` automatically re-format all instances.
|
|
94
|
+
|
|
95
|
+
## Examples
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
<!-- Default (medium) -->
|
|
99
|
+
<div class="form-element">
|
|
100
|
+
<label for="birthday">Birthday</label>
|
|
101
|
+
<input type="date" id="birthday" name="birthday" data-ln-date>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<!-- Short format -->
|
|
105
|
+
<div class="form-element">
|
|
106
|
+
<label for="due">Due Date</label>
|
|
107
|
+
<input type="date" id="due" name="due" data-ln-date="short">
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<!-- Long format -->
|
|
111
|
+
<div class="form-element">
|
|
112
|
+
<label for="event">Event Date</label>
|
|
113
|
+
<input type="date" id="event" name="event" data-ln-date="long">
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<!-- Custom pattern -->
|
|
117
|
+
<div class="form-element">
|
|
118
|
+
<label for="start">Start Date</label>
|
|
119
|
+
<input type="date" id="start" name="start" data-ln-date="dd.MM.yyyy">
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<!-- Pre-filled value -->
|
|
123
|
+
<div class="form-element">
|
|
124
|
+
<label for="hired">Hire Date</label>
|
|
125
|
+
<input type="date" id="hired" name="hired" value="2024-03-15" data-ln-date>
|
|
126
|
+
</div>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Integration with ln-form
|
|
130
|
+
|
|
131
|
+
Works automatically. The hidden input carries the `name` attribute, so form serialization and population go through it transparently.
|
|
132
|
+
|
|
133
|
+
## Integration and Source Files
|
|
134
|
+
|
|
135
|
+
This component can be loaded either as part of the unified Ashlar bundle or as a standalone zero-dependency script.
|
|
136
|
+
|
|
137
|
+
### 1. In-Bundle (Standard Integration)
|
|
138
|
+
Include the main Ashlar bundle in your HTML. This bundle registers all Ashlar components, including `ln-date`:
|
|
139
|
+
```html
|
|
140
|
+
<script src="dist/ln-ashlar.iife.js" defer></script>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 2. Standalone (Zero-Dependency IIFE)
|
|
144
|
+
If you only need date-formatting functionality without the rest of the Ashlar library, load the component's standalone IIFE script:
|
|
145
|
+
```html
|
|
146
|
+
<script src="js/ln-date/ln-date.js" defer></script>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 3. Source & Reference
|
|
150
|
+
* **Active Development Source**: [js/ln-date/src/ln-date.js](file:///c:/laragon/www/ln-ashlar/js/ln-date/src/ln-date.js) — The source of truth containing the ES module implementation.
|
|
151
|
+
* **Compiled Standalone Release**: [js/ln-date/ln-date.js](file:///c:/laragon/www/ln-ashlar/js/ln-date/ln-date.js) — The compiled IIFE distribution file.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";function b(s,i,p){s.dispatchEvent(new CustomEvent(i,{bubbles:!0,detail:p||{}}))}function C(s,i){if(!document.body){document.addEventListener("DOMContentLoaded",function(){C(s,i)}),console.warn("["+i+'] Script loaded before <body> — add "defer" to your <script> tag');return}s()}function I(s,i,p,u){if(s.nodeType!==1)return;const M=i.indexOf("[")!==-1||i.indexOf(".")!==-1||i.indexOf("#")!==-1?i:"["+i+"]",S=Array.from(s.querySelectorAll(M));s.matches&&s.matches(M)&&S.push(s);for(const g of S)g[p]||(g[p]=new u(g))}function N(s){const i=s.closest("[lang]");return(i?i.lang:null)||navigator.language}function L(s,i,p,u,_={}){const M=_.extraAttributes||[],S=_.onAttributeChange||null,g=_.onInit||null;function w(O){const f=O||document.body;I(f,s,i,p),g&&g(f)}return C(function(){const O=new MutationObserver(function(h){for(let v=0;v<h.length;v++){const y=h[v];if(y.type==="childList")for(let t=0;t<y.addedNodes.length;t++){const e=y.addedNodes[t];e.nodeType===1&&(I(e,s,i,p),g&&g(e))}else y.type==="attributes"&&(S&&y.target[i]?S(y.target,y.attributeName):(I(y.target,s,i,p),g&&g(y.target)))}});let f=[];if(s.indexOf("[")!==-1){const h=/\[([\w-]+)/g;let v;for(;(v=h.exec(s))!==null;)f.push(v[1])}else f.push(s);O.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:f.concat(M)})},u),window[i]=w,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){w(document.body)}):w(document.body),w}const A={};function P(s,i){A[s]=i}function x(s){return A[s]||{ingress:i=>i,egress:i=>i}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=P,window.lnCore.getDataMapper=x),(function(){const s="data-ln-date",i="lnDate";if(window[i]!==void 0)return;const p={},u=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,"value");function _(t,e){const n=t+"|"+JSON.stringify(e);return p[n]||(p[n]=new Intl.DateTimeFormat(t,e)),p[n]}const M=/^(short|medium|long)(\s+datetime)?$/,S={short:{dateStyle:"short"},medium:{dateStyle:"medium"},long:{dateStyle:"long"},"short datetime":{dateStyle:"short",timeStyle:"short"},"medium datetime":{dateStyle:"medium",timeStyle:"short"},"long datetime":{dateStyle:"long",timeStyle:"short"}};function g(t){return!t||t===""?{dateStyle:"medium"}:t.match(M)?S[t]:null}function w(t,e,n){const o=t.getDate(),d=t.getMonth(),a=t.getFullYear(),c=t.getHours(),r=t.getMinutes(),l={yyyy:String(a),yy:String(a).slice(-2),MMMM:_(n,{month:"long"}).format(t),MMM:_(n,{month:"short"}).format(t),MM:String(d+1).padStart(2,"0"),M:String(d+1),dd:String(o).padStart(2,"0"),d:String(o),HH:String(c).padStart(2,"0"),mm:String(r).padStart(2,"0")};return e.replace(/yyyy|yy|MMMM|MMM|MM|M|dd|d|HH|mm/g,function(m){return l[m]})}function O(t,e,n){const o=g(e);return o?_(n,o).format(t):w(t,e,n)}function f(t){if(t.tagName!=="INPUT")return console.warn("[ln-date] Can only be applied to <input>, got:",t.tagName),this;this.dom=t;const e=this,n=t.value,o=t.name,d=document.createElement("input");d.type="hidden",d.name=o,t.removeAttribute("name"),t.insertAdjacentElement("afterend",d),this._hidden=d;const a=document.createElement("input");a.type="date",a.tabIndex=-1,a.style.cssText="position:absolute;opacity:0;width:0;height:0;overflow:hidden;pointer-events:none",d.insertAdjacentElement("afterend",a),this._picker=a,t.type="text";const c=document.createElement("button");if(c.type="button",c.setAttribute("aria-label","Open date picker"),c.innerHTML='<svg class="ln-icon" aria-hidden="true"><use href="#ln-calendar"></use></svg>',a.insertAdjacentElement("afterend",c),this._btn=c,this._lastISO="",Object.defineProperty(d,"value",{get:function(){return u.get.call(d)},set:function(r){if(u.set.call(d,r),r&&r!==""){const l=h(r);l&&(e._displayFormatted(l),u.set.call(a,r))}else r===""&&(e.dom.value="",u.set.call(a,""))}}),this._onPickerChange=function(){const r=a.value;if(r){const l=h(r);l&&(e._setHiddenRaw(r),e._displayFormatted(l),e._lastISO=r,b(e.dom,"ln-date:change",{value:r,formatted:e.dom.value,date:l}))}else e._setHiddenRaw(""),e.dom.value="",e._lastISO="",b(e.dom,"ln-date:change",{value:"",formatted:"",date:null})},a.addEventListener("change",this._onPickerChange),this._onBlur=function(){const r=e.dom.value.trim();if(r===""){e._lastISO!==""&&(e._setHiddenRaw(""),u.set.call(e._picker,""),e.dom.value="",e._lastISO="",b(e.dom,"ln-date:change",{value:"",formatted:"",date:null}));return}if(e._lastISO){const m=h(e._lastISO);if(m){const E=e.dom.getAttribute(s)||"",D=N(e.dom),k=O(m,E,D);if(r===k)return}}const l=v(r);if(l){const m=l.getFullYear(),E=String(l.getMonth()+1).padStart(2,"0"),D=String(l.getDate()).padStart(2,"0"),k=m+"-"+E+"-"+D;e._setHiddenRaw(k),u.set.call(e._picker,k),e._displayFormatted(l),e._lastISO=k,b(e.dom,"ln-date:change",{value:k,formatted:e.dom.value,date:l})}else if(e._lastISO){const m=h(e._lastISO);m&&e._displayFormatted(m)}else e.dom.value=""},t.addEventListener("blur",this._onBlur),this._onBtnClick=function(){e._openPicker()},c.addEventListener("click",this._onBtnClick),n&&n!==""){const r=h(n);r&&(this._setHiddenRaw(n),u.set.call(a,n),this._displayFormatted(r),this._lastISO=n)}return this}function h(t){if(!t||typeof t!="string")return null;const e=t.split("T"),n=e[0].split("-");if(n.length<3)return null;const o=parseInt(n[0],10),d=parseInt(n[1],10)-1,a=parseInt(n[2],10);if(isNaN(o)||isNaN(d)||isNaN(a))return null;let c=0,r=0;if(e[1]){const m=e[1].split(":");c=parseInt(m[0],10)||0,r=parseInt(m[1],10)||0}const l=new Date(o,d,a,c,r);return l.getFullYear()!==o||l.getMonth()!==d||l.getDate()!==a?null:l}function v(t){if(!t||typeof t!="string"||(t=t.trim(),t.length<6))return null;let e,n;if(t.indexOf(".")!==-1)e=".",n=t.split(".");else if(t.indexOf("/")!==-1)e="/",n=t.split("/");else if(t.indexOf("-")!==-1)e="-",n=t.split("-");else return null;if(n.length!==3)return null;const o=[];for(let l=0;l<3;l++){const m=parseInt(n[l],10);if(isNaN(m))return null;o.push(m)}let d,a,c;e==="."?(d=o[0],a=o[1],c=o[2]):e==="/"?(a=o[0],d=o[1],c=o[2]):n[0].length===4?(c=o[0],a=o[1],d=o[2]):(d=o[0],a=o[1],c=o[2]),c<100&&(c+=c<50?2e3:1900);const r=new Date(c,a-1,d);return r.getFullYear()!==c||r.getMonth()!==a-1||r.getDate()!==d?null:r}f.prototype._openPicker=function(){if(typeof this._picker.showPicker=="function")try{this._picker.showPicker()}catch{this._picker.click()}else this._picker.click()},f.prototype._setHiddenRaw=function(t){u.set.call(this._hidden,t)},f.prototype._displayFormatted=function(t){const e=this.dom.getAttribute(s)||"",n=N(this.dom);this.dom.value=O(t,e,n)},Object.defineProperty(f.prototype,"value",{get:function(){return u.get.call(this._hidden)},set:function(t){if(!t||t===""){this._setHiddenRaw(""),u.set.call(this._picker,""),this.dom.value="",this._lastISO="";return}const e=h(t);e&&(this._setHiddenRaw(t),u.set.call(this._picker,t),this._displayFormatted(e),this._lastISO=t,b(this.dom,"ln-date:change",{value:t,formatted:this.dom.value,date:e}))}}),Object.defineProperty(f.prototype,"date",{get:function(){const t=this.value;return t?h(t):null},set:function(t){if(!t||!(t instanceof Date)||isNaN(t.getTime())){this.value="";return}const e=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),o=String(t.getDate()).padStart(2,"0");this.value=e+"-"+n+"-"+o}}),Object.defineProperty(f.prototype,"formatted",{get:function(){return this.dom.value}}),f.prototype.destroy=function(){if(!this.dom[i])return;this._picker.removeEventListener("change",this._onPickerChange),this.dom.removeEventListener("blur",this._onBlur),this._btn.removeEventListener("click",this._onBtnClick),this.dom.name=this._hidden.name,this.dom.type="date";const t=this.value;this._hidden.remove(),this._picker.remove(),this._btn.remove(),t&&(this.dom.value=t),b(this.dom,"ln-date:destroyed",{target:this.dom}),delete this.dom[i]};function y(){new MutationObserver(function(){const t=document.querySelectorAll("["+s+"]");for(let e=0;e<t.length;e++){const n=t[e][i];if(n&&n.value){const o=h(n.value);o&&n._displayFormatted(o)}}}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"]})}L(s,i,f,"ln-date"),y()})()})();
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { dispatch, getLocale, registerComponent } from '../../ln-core';
|
|
2
|
+
|
|
3
|
+
(function () {
|
|
4
|
+
const DOM_SELECTOR = 'data-ln-date';
|
|
5
|
+
const DOM_ATTRIBUTE = 'lnDate';
|
|
6
|
+
|
|
7
|
+
if (window[DOM_ATTRIBUTE] !== undefined) return;
|
|
8
|
+
|
|
9
|
+
// ─── Formatter Cache ──────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
const _formatters = {};
|
|
12
|
+
const _inputValueDesc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
|
|
13
|
+
|
|
14
|
+
function _getFormatter(locale, options) {
|
|
15
|
+
const key = locale + '|' + JSON.stringify(options);
|
|
16
|
+
if (!_formatters[key]) {
|
|
17
|
+
_formatters[key] = new Intl.DateTimeFormat(locale, options);
|
|
18
|
+
}
|
|
19
|
+
return _formatters[key];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ─── Format Detection ─────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const KEYWORD_RE = /^(short|medium|long)(\s+datetime)?$/;
|
|
25
|
+
|
|
26
|
+
const KEYWORD_OPTIONS = {
|
|
27
|
+
'short': { dateStyle: 'short' },
|
|
28
|
+
'medium': { dateStyle: 'medium' },
|
|
29
|
+
'long': { dateStyle: 'long' },
|
|
30
|
+
'short datetime': { dateStyle: 'short', timeStyle: 'short' },
|
|
31
|
+
'medium datetime': { dateStyle: 'medium', timeStyle: 'short' },
|
|
32
|
+
'long datetime': { dateStyle: 'long', timeStyle: 'short' }
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function _getIntlOptions(format) {
|
|
36
|
+
if (!format || format === '') return { dateStyle: 'medium' };
|
|
37
|
+
const match = format.match(KEYWORD_RE);
|
|
38
|
+
if (match) {
|
|
39
|
+
return KEYWORD_OPTIONS[format];
|
|
40
|
+
}
|
|
41
|
+
return null; // custom pattern
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Custom Pattern Formatting ────────────────────────────
|
|
45
|
+
|
|
46
|
+
function _formatCustom(date, pattern, locale) {
|
|
47
|
+
const day = date.getDate();
|
|
48
|
+
const month = date.getMonth();
|
|
49
|
+
const year = date.getFullYear();
|
|
50
|
+
const hours = date.getHours();
|
|
51
|
+
const minutes = date.getMinutes();
|
|
52
|
+
|
|
53
|
+
const tokens = {
|
|
54
|
+
'yyyy': String(year),
|
|
55
|
+
'yy': String(year).slice(-2),
|
|
56
|
+
'MMMM': _getFormatter(locale, { month: 'long' }).format(date),
|
|
57
|
+
'MMM': _getFormatter(locale, { month: 'short' }).format(date),
|
|
58
|
+
'MM': String(month + 1).padStart(2, '0'),
|
|
59
|
+
'M': String(month + 1),
|
|
60
|
+
'dd': String(day).padStart(2, '0'),
|
|
61
|
+
'd': String(day),
|
|
62
|
+
'HH': String(hours).padStart(2, '0'),
|
|
63
|
+
'mm': String(minutes).padStart(2, '0')
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return pattern.replace(/yyyy|yy|MMMM|MMM|MM|M|dd|d|HH|mm/g, function (m) { return tokens[m]; });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Format Date ──────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
function _formatDate(date, format, locale) {
|
|
72
|
+
const intlOptions = _getIntlOptions(format);
|
|
73
|
+
if (intlOptions) {
|
|
74
|
+
return _getFormatter(locale, intlOptions).format(date);
|
|
75
|
+
}
|
|
76
|
+
return _formatCustom(date, format, locale);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Component ────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
function _component(dom) {
|
|
82
|
+
if (dom.tagName !== 'INPUT') {
|
|
83
|
+
console.warn('[ln-date] Can only be applied to <input>, got:', dom.tagName);
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.dom = dom;
|
|
88
|
+
const self = this;
|
|
89
|
+
|
|
90
|
+
// ── Read initial state ──────────────────────────────
|
|
91
|
+
const initialValue = dom.value; // ISO string (YYYY-MM-DD) or empty
|
|
92
|
+
const name = dom.name;
|
|
93
|
+
|
|
94
|
+
// ── Create hidden input for form submission ─────────
|
|
95
|
+
const hidden = document.createElement('input');
|
|
96
|
+
hidden.type = 'hidden';
|
|
97
|
+
hidden.name = name;
|
|
98
|
+
dom.removeAttribute('name');
|
|
99
|
+
dom.insertAdjacentElement('afterend', hidden);
|
|
100
|
+
this._hidden = hidden;
|
|
101
|
+
|
|
102
|
+
// ── Create hidden date input for native picker ──────
|
|
103
|
+
const picker = document.createElement('input');
|
|
104
|
+
picker.type = 'date';
|
|
105
|
+
picker.tabIndex = -1;
|
|
106
|
+
picker.style.cssText = 'position:absolute;opacity:0;width:0;height:0;overflow:hidden;pointer-events:none';
|
|
107
|
+
hidden.insertAdjacentElement('afterend', picker);
|
|
108
|
+
this._picker = picker;
|
|
109
|
+
|
|
110
|
+
// ── Transform original input to text display ────────
|
|
111
|
+
dom.type = 'text';
|
|
112
|
+
|
|
113
|
+
// ── Create calendar button ──────────────────────────
|
|
114
|
+
const btn = document.createElement('button');
|
|
115
|
+
btn.type = 'button';
|
|
116
|
+
btn.setAttribute('aria-label', 'Open date picker');
|
|
117
|
+
btn.innerHTML = '<svg class="ln-icon" aria-hidden="true"><use href="#ln-calendar"></use></svg>';
|
|
118
|
+
picker.insertAdjacentElement('afterend', btn);
|
|
119
|
+
this._btn = btn;
|
|
120
|
+
this._lastISO = '';
|
|
121
|
+
|
|
122
|
+
// ── Intercept programmatic value sets on hidden input
|
|
123
|
+
Object.defineProperty(hidden, 'value', {
|
|
124
|
+
get: function () {
|
|
125
|
+
return _inputValueDesc.get.call(hidden);
|
|
126
|
+
},
|
|
127
|
+
set: function (val) {
|
|
128
|
+
_inputValueDesc.set.call(hidden, val);
|
|
129
|
+
if (val && val !== '') {
|
|
130
|
+
const date = _parseISO(val);
|
|
131
|
+
if (date) {
|
|
132
|
+
self._displayFormatted(date);
|
|
133
|
+
_inputValueDesc.set.call(picker, val);
|
|
134
|
+
}
|
|
135
|
+
} else if (val === '') {
|
|
136
|
+
self.dom.value = '';
|
|
137
|
+
_inputValueDesc.set.call(picker, '');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ── Bind events ─────────────────────────────────────
|
|
143
|
+
this._onPickerChange = function () {
|
|
144
|
+
const val = picker.value; // ISO YYYY-MM-DD
|
|
145
|
+
if (val) {
|
|
146
|
+
const date = _parseISO(val);
|
|
147
|
+
if (date) {
|
|
148
|
+
self._setHiddenRaw(val);
|
|
149
|
+
self._displayFormatted(date);
|
|
150
|
+
self._lastISO = val;
|
|
151
|
+
dispatch(self.dom, 'ln-date:change', {
|
|
152
|
+
value: val,
|
|
153
|
+
formatted: self.dom.value,
|
|
154
|
+
date: date
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
self._setHiddenRaw('');
|
|
159
|
+
self.dom.value = '';
|
|
160
|
+
self._lastISO = '';
|
|
161
|
+
dispatch(self.dom, 'ln-date:change', {
|
|
162
|
+
value: '',
|
|
163
|
+
formatted: '',
|
|
164
|
+
date: null
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
picker.addEventListener('change', this._onPickerChange);
|
|
169
|
+
|
|
170
|
+
this._onBlur = function () {
|
|
171
|
+
const typed = self.dom.value.trim();
|
|
172
|
+
|
|
173
|
+
// Empty input — clear if there was a value
|
|
174
|
+
if (typed === '') {
|
|
175
|
+
if (self._lastISO !== '') {
|
|
176
|
+
self._setHiddenRaw('');
|
|
177
|
+
_inputValueDesc.set.call(self._picker, '');
|
|
178
|
+
self.dom.value = '';
|
|
179
|
+
self._lastISO = '';
|
|
180
|
+
dispatch(self.dom, 'ln-date:change', {
|
|
181
|
+
value: '',
|
|
182
|
+
formatted: '',
|
|
183
|
+
date: null
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check if text is unchanged from current formatted display
|
|
190
|
+
if (self._lastISO) {
|
|
191
|
+
const currentDate = _parseISO(self._lastISO);
|
|
192
|
+
if (currentDate) {
|
|
193
|
+
const format = self.dom.getAttribute(DOM_SELECTOR) || '';
|
|
194
|
+
const locale = getLocale(self.dom);
|
|
195
|
+
const currentFormatted = _formatDate(currentDate, format, locale);
|
|
196
|
+
if (typed === currentFormatted) return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Try to parse the typed value
|
|
201
|
+
const parsed = _parseTyped(typed);
|
|
202
|
+
if (parsed) {
|
|
203
|
+
const y = parsed.getFullYear();
|
|
204
|
+
const m = String(parsed.getMonth() + 1).padStart(2, '0');
|
|
205
|
+
const d = String(parsed.getDate()).padStart(2, '0');
|
|
206
|
+
const iso = y + '-' + m + '-' + d;
|
|
207
|
+
self._setHiddenRaw(iso);
|
|
208
|
+
_inputValueDesc.set.call(self._picker, iso);
|
|
209
|
+
self._displayFormatted(parsed);
|
|
210
|
+
self._lastISO = iso;
|
|
211
|
+
dispatch(self.dom, 'ln-date:change', {
|
|
212
|
+
value: iso,
|
|
213
|
+
formatted: self.dom.value,
|
|
214
|
+
date: parsed
|
|
215
|
+
});
|
|
216
|
+
} else {
|
|
217
|
+
// Invalid input — revert to previous display
|
|
218
|
+
if (self._lastISO) {
|
|
219
|
+
const prevDate = _parseISO(self._lastISO);
|
|
220
|
+
if (prevDate) self._displayFormatted(prevDate);
|
|
221
|
+
} else {
|
|
222
|
+
self.dom.value = '';
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
dom.addEventListener('blur', this._onBlur);
|
|
227
|
+
|
|
228
|
+
this._onBtnClick = function () {
|
|
229
|
+
self._openPicker();
|
|
230
|
+
};
|
|
231
|
+
btn.addEventListener('click', this._onBtnClick);
|
|
232
|
+
|
|
233
|
+
// ── Handle pre-filled value ─────────────────────────
|
|
234
|
+
if (initialValue && initialValue !== '') {
|
|
235
|
+
const date = _parseISO(initialValue);
|
|
236
|
+
if (date) {
|
|
237
|
+
this._setHiddenRaw(initialValue);
|
|
238
|
+
_inputValueDesc.set.call(picker, initialValue);
|
|
239
|
+
this._displayFormatted(date);
|
|
240
|
+
this._lastISO = initialValue;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return this;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ─── Helpers ──────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
function _parseISO(str) {
|
|
250
|
+
// Parse YYYY-MM-DD (and optionally YYYY-MM-DDTHH:mm)
|
|
251
|
+
if (!str || typeof str !== 'string') return null;
|
|
252
|
+
const parts = str.split('T');
|
|
253
|
+
const dateParts = parts[0].split('-');
|
|
254
|
+
if (dateParts.length < 3) return null;
|
|
255
|
+
const y = parseInt(dateParts[0], 10);
|
|
256
|
+
const m = parseInt(dateParts[1], 10) - 1;
|
|
257
|
+
const d = parseInt(dateParts[2], 10);
|
|
258
|
+
if (isNaN(y) || isNaN(m) || isNaN(d)) return null;
|
|
259
|
+
let h = 0, min = 0;
|
|
260
|
+
if (parts[1]) {
|
|
261
|
+
const timeParts = parts[1].split(':');
|
|
262
|
+
h = parseInt(timeParts[0], 10) || 0;
|
|
263
|
+
min = parseInt(timeParts[1], 10) || 0;
|
|
264
|
+
}
|
|
265
|
+
const date = new Date(y, m, d, h, min);
|
|
266
|
+
// Validate the date is real (e.g., Feb 30 would roll over)
|
|
267
|
+
if (date.getFullYear() !== y || date.getMonth() !== m || date.getDate() !== d) return null;
|
|
268
|
+
return date;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function _parseTyped(str) {
|
|
272
|
+
if (!str || typeof str !== 'string') return null;
|
|
273
|
+
str = str.trim();
|
|
274
|
+
if (str.length < 6) return null; // minimum: d.M.yy
|
|
275
|
+
|
|
276
|
+
// Detect separator
|
|
277
|
+
let sep, parts;
|
|
278
|
+
if (str.indexOf('.') !== -1) {
|
|
279
|
+
sep = '.';
|
|
280
|
+
parts = str.split('.');
|
|
281
|
+
} else if (str.indexOf('/') !== -1) {
|
|
282
|
+
sep = '/';
|
|
283
|
+
parts = str.split('/');
|
|
284
|
+
} else if (str.indexOf('-') !== -1) {
|
|
285
|
+
sep = '-';
|
|
286
|
+
parts = str.split('-');
|
|
287
|
+
} else {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (parts.length !== 3) return null;
|
|
292
|
+
const nums = [];
|
|
293
|
+
for (let i = 0; i < 3; i++) {
|
|
294
|
+
const n = parseInt(parts[i], 10);
|
|
295
|
+
if (isNaN(n)) return null;
|
|
296
|
+
nums.push(n);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let day, month, year;
|
|
300
|
+
|
|
301
|
+
if (sep === '.') {
|
|
302
|
+
// dd.MM.yyyy (European)
|
|
303
|
+
day = nums[0]; month = nums[1]; year = nums[2];
|
|
304
|
+
} else if (sep === '/') {
|
|
305
|
+
// MM/dd/yyyy (US)
|
|
306
|
+
month = nums[0]; day = nums[1]; year = nums[2];
|
|
307
|
+
} else {
|
|
308
|
+
// dash: yyyy-MM-dd if first part is 4 digits, else dd-MM-yyyy
|
|
309
|
+
if (parts[0].length === 4) {
|
|
310
|
+
year = nums[0]; month = nums[1]; day = nums[2];
|
|
311
|
+
} else {
|
|
312
|
+
day = nums[0]; month = nums[1]; year = nums[2];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Two-digit year pivot
|
|
317
|
+
if (year < 100) {
|
|
318
|
+
year += (year < 50) ? 2000 : 1900;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Validate via Date constructor roundtrip
|
|
322
|
+
const date = new Date(year, month - 1, day);
|
|
323
|
+
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return date;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
_component.prototype._openPicker = function () {
|
|
330
|
+
if (typeof this._picker.showPicker === 'function') {
|
|
331
|
+
try {
|
|
332
|
+
this._picker.showPicker();
|
|
333
|
+
} catch (e) {
|
|
334
|
+
// showPicker() can throw if not triggered by user gesture
|
|
335
|
+
this._picker.click();
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
this._picker.click();
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
_component.prototype._setHiddenRaw = function (val) {
|
|
343
|
+
_inputValueDesc.set.call(this._hidden, val);
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
_component.prototype._displayFormatted = function (date) {
|
|
347
|
+
const format = this.dom.getAttribute(DOM_SELECTOR) || '';
|
|
348
|
+
const locale = getLocale(this.dom);
|
|
349
|
+
this.dom.value = _formatDate(date, format, locale);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// ─── Public API ───────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
Object.defineProperty(_component.prototype, 'value', {
|
|
355
|
+
get: function () {
|
|
356
|
+
return _inputValueDesc.get.call(this._hidden);
|
|
357
|
+
},
|
|
358
|
+
set: function (isoStr) {
|
|
359
|
+
if (!isoStr || isoStr === '') {
|
|
360
|
+
this._setHiddenRaw('');
|
|
361
|
+
_inputValueDesc.set.call(this._picker, '');
|
|
362
|
+
this.dom.value = '';
|
|
363
|
+
this._lastISO = '';
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const date = _parseISO(isoStr);
|
|
367
|
+
if (!date) return;
|
|
368
|
+
this._setHiddenRaw(isoStr);
|
|
369
|
+
_inputValueDesc.set.call(this._picker, isoStr);
|
|
370
|
+
this._displayFormatted(date);
|
|
371
|
+
this._lastISO = isoStr;
|
|
372
|
+
dispatch(this.dom, 'ln-date:change', {
|
|
373
|
+
value: isoStr,
|
|
374
|
+
formatted: this.dom.value,
|
|
375
|
+
date: date
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
Object.defineProperty(_component.prototype, 'date', {
|
|
381
|
+
get: function () {
|
|
382
|
+
const val = this.value;
|
|
383
|
+
if (!val) return null;
|
|
384
|
+
return _parseISO(val);
|
|
385
|
+
},
|
|
386
|
+
set: function (dateObj) {
|
|
387
|
+
if (!dateObj || !(dateObj instanceof Date) || isNaN(dateObj.getTime())) {
|
|
388
|
+
this.value = '';
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const y = dateObj.getFullYear();
|
|
392
|
+
const m = String(dateObj.getMonth() + 1).padStart(2, '0');
|
|
393
|
+
const d = String(dateObj.getDate()).padStart(2, '0');
|
|
394
|
+
this.value = y + '-' + m + '-' + d;
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
Object.defineProperty(_component.prototype, 'formatted', {
|
|
399
|
+
get: function () {
|
|
400
|
+
return this.dom.value;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
_component.prototype.destroy = function () {
|
|
405
|
+
if (!this.dom[DOM_ATTRIBUTE]) return;
|
|
406
|
+
this._picker.removeEventListener('change', this._onPickerChange);
|
|
407
|
+
this.dom.removeEventListener('blur', this._onBlur);
|
|
408
|
+
this._btn.removeEventListener('click', this._onBtnClick);
|
|
409
|
+
// Restore original input
|
|
410
|
+
this.dom.name = this._hidden.name;
|
|
411
|
+
this.dom.type = 'date';
|
|
412
|
+
const isoVal = this.value;
|
|
413
|
+
// Remove created elements
|
|
414
|
+
this._hidden.remove();
|
|
415
|
+
this._picker.remove();
|
|
416
|
+
this._btn.remove();
|
|
417
|
+
// Restore value
|
|
418
|
+
if (isoVal) this.dom.value = isoVal;
|
|
419
|
+
dispatch(this.dom, 'ln-date:destroyed', { target: this.dom });
|
|
420
|
+
delete this.dom[DOM_ATTRIBUTE];
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// ─── Locale Observer ──────────────────────────────────────
|
|
424
|
+
|
|
425
|
+
function _localeObserver() {
|
|
426
|
+
new MutationObserver(function () {
|
|
427
|
+
const els = document.querySelectorAll('[' + DOM_SELECTOR + ']');
|
|
428
|
+
for (let i = 0; i < els.length; i++) {
|
|
429
|
+
const inst = els[i][DOM_ATTRIBUTE];
|
|
430
|
+
if (inst && inst.value) {
|
|
431
|
+
const date = _parseISO(inst.value);
|
|
432
|
+
if (date) inst._displayFormatted(date);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}).observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ─── Init ─────────────────────────────────────────────────
|
|
439
|
+
|
|
440
|
+
registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-date');
|
|
441
|
+
_localeObserver();
|
|
442
|
+
})();
|