kempo-ui 0.4.1 → 0.4.3
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/dist/components/Calendar.js +170 -0
- package/dist/components/Slider.js +188 -0
- package/dist/components/Time.js +37 -0
- package/docs/components/accordion.html +12 -0
- package/docs/components/aside.html +12 -0
- package/docs/components/calendar.html +712 -0
- package/docs/components/card.html +12 -0
- package/docs/components/code-editor.html +12 -0
- package/docs/components/color-picker.html +12 -0
- package/docs/components/combobox.html +12 -0
- package/docs/components/content-slider.html +12 -0
- package/docs/components/context.html +12 -0
- package/docs/components/dialog.html +12 -0
- package/docs/components/dropdown.html +12 -0
- package/docs/components/filter-list.html +12 -0
- package/docs/components/focus-capture.html +12 -0
- package/docs/components/html-editor.html +12 -0
- package/docs/components/hybrid-component.html +12 -0
- package/docs/components/icon.html +12 -0
- package/docs/components/import.html +12 -0
- package/docs/components/light-component.html +12 -0
- package/docs/components/nav-spacer.html +12 -0
- package/docs/components/nav.html +12 -0
- package/docs/components/photo-viewer.html +12 -0
- package/docs/components/progress.html +12 -0
- package/docs/components/resize.html +12 -0
- package/docs/components/shadow-component.html +12 -0
- package/docs/components/show-more.html +12 -0
- package/docs/components/slider.html +789 -0
- package/docs/components/sortable.html +12 -0
- package/docs/components/spinner.html +12 -0
- package/docs/components/split.html +12 -0
- package/docs/components/table.html +12 -0
- package/docs/components/tableControls.html +12 -0
- package/docs/components/tableCustomFields.html +12 -0
- package/docs/components/tableFetchRecords.html +12 -0
- package/docs/components/tableFieldSortHide.html +12 -0
- package/docs/components/tablePagination.html +12 -0
- package/docs/components/tablePlaceholder.html +12 -0
- package/docs/components/tableRecordEditing.html +12 -0
- package/docs/components/tableRecordFiltering.html +12 -0
- package/docs/components/tableRecordHiding.html +12 -0
- package/docs/components/tableRecordSearching.html +12 -0
- package/docs/components/tableRecordSelection.html +12 -0
- package/docs/components/tableRowControls.html +12 -0
- package/docs/components/tableServerSync.html +12 -0
- package/docs/components/tableSorting.html +12 -0
- package/docs/components/tabs.html +12 -0
- package/docs/components/tags.html +12 -0
- package/docs/components/theme-select.html +12 -0
- package/docs/components/theme-switcher.html +12 -0
- package/docs/components/time.html +662 -0
- package/docs/components/timestamp.html +12 -0
- package/docs/components/toast.html +12 -0
- package/docs/components/toggle.html +12 -0
- package/docs/components/tree.html +12 -0
- package/docs/index.html +30 -0
- package/docs/src/components/Calendar.js +170 -0
- package/docs/src/components/Slider.js +188 -0
- package/docs/src/components/Time.js +37 -0
- package/docs/utils/context.html +12 -0
- package/docs/utils/cookie.html +12 -0
- package/docs/utils/debounce.html +12 -0
- package/docs/utils/drag.html +12 -0
- package/docs/utils/elevation.html +12 -0
- package/docs/utils/formatTimestamp.html +12 -0
- package/docs/utils/object.html +12 -0
- package/docs/utils/propConverters.html +12 -0
- package/docs/utils/string.html +12 -0
- package/docs/utils/theme.html +12 -0
- package/docs/utils/toTitleCase.html +12 -0
- package/docs/utils/type.html +12 -0
- package/docs/utils/wait.html +12 -0
- package/docs-src/components/calendar.page.html +254 -0
- package/docs-src/components/slider.page.html +331 -0
- package/docs-src/components/time.page.html +204 -0
- package/docs-src/index.page.html +18 -0
- package/docs-src/nav.fragment.html +12 -0
- package/llms.txt +3 -0
- package/package.json +1 -1
- package/src/components/Calendar.js +473 -0
- package/src/components/Slider.js +480 -0
- package/src/components/Time.js +137 -0
- package/tests/components/Slider.browser-test.js +823 -0
package/docs-src/index.page.html
CHANGED
|
@@ -47,6 +47,12 @@
|
|
|
47
47
|
<p class="tc-muted">Enhanced sidebar component with push and overlay modes.</p>
|
|
48
48
|
</a>
|
|
49
49
|
</div>
|
|
50
|
+
<div class="span-12 t-span-6 d-span-4 px">
|
|
51
|
+
<a href="{{pathToRoot}}components/calendar.html" class="card mb no-link d-b">
|
|
52
|
+
<h3 class="tc-primary">Calendar</h3>
|
|
53
|
+
<p class="tc-muted">Month-grid date picker widget with single and range selection modes.</p>
|
|
54
|
+
</a>
|
|
55
|
+
</div>
|
|
50
56
|
<div class="span-12 t-span-6 d-span-4 px">
|
|
51
57
|
<a href="{{pathToRoot}}components/card.html" class="card mb no-link d-b">
|
|
52
58
|
<h3 class="tc-primary">Card</h3>
|
|
@@ -161,6 +167,12 @@
|
|
|
161
167
|
<p class="tc-muted">Truncate long content with a "show more" button to expand.</p>
|
|
162
168
|
</a>
|
|
163
169
|
</div>
|
|
170
|
+
<div class="span-12 t-span-6 d-span-4 px">
|
|
171
|
+
<a href="{{pathToRoot}}components/slider.html" class="card mb no-link d-b">
|
|
172
|
+
<h3 class="tc-primary">Slider</h3>
|
|
173
|
+
<p class="tc-muted">Range input with configurable min, max, and discrete step snap points.</p>
|
|
174
|
+
</a>
|
|
175
|
+
</div>
|
|
164
176
|
<div class="span-12 t-span-6 d-span-4 px">
|
|
165
177
|
<a href="{{pathToRoot}}components/sortable.html" class="card mb no-link d-b">
|
|
166
178
|
<h3 class="tc-primary">Sortable</h3>
|
|
@@ -209,6 +221,12 @@
|
|
|
209
221
|
<p class="tc-muted">Toggle between light, dark, and auto theme modes.</p>
|
|
210
222
|
</a>
|
|
211
223
|
</div>
|
|
224
|
+
<div class="span-12 t-span-6 d-span-4 px">
|
|
225
|
+
<a href="{{pathToRoot}}components/time.html" class="card mb no-link d-b">
|
|
226
|
+
<h3 class="tc-primary">Time</h3>
|
|
227
|
+
<p class="tc-muted">Time picker with 12/24-hour display, configurable minute increments, and form submission.</p>
|
|
228
|
+
</a>
|
|
229
|
+
</div>
|
|
212
230
|
<div class="span-12 t-span-6 d-span-4 px">
|
|
213
231
|
<a href="{{pathToRoot}}components/timestamp.html" class="card mb no-link d-b">
|
|
214
232
|
<h3 class="tc-primary">Timestamp</h3>
|
|
@@ -46,6 +46,9 @@
|
|
|
46
46
|
href="{{pathToRoot}}components/accordion.html">Accordion<br><small>Component</small></a></k-filter-item>
|
|
47
47
|
<k-filter-item filter-keywords="aside sidebar components"><a
|
|
48
48
|
href="{{pathToRoot}}components/aside.html">Aside<br><small>Component</small></a></k-filter-item>
|
|
49
|
+
<k-filter-item filter-keywords="calendar date picker range month day components"><a
|
|
50
|
+
href="{{pathToRoot}}components/calendar.html"
|
|
51
|
+
>Calendar<br><small>Component</small></a></k-filter-item>
|
|
49
52
|
<k-filter-item filter-keywords="card components"><a
|
|
50
53
|
href="{{pathToRoot}}components/card.html">Card<br><small>Component</small></a></k-filter-item>
|
|
51
54
|
<k-filter-item filter-keywords="code editor codeeditor monaco components"><a
|
|
@@ -95,6 +98,9 @@
|
|
|
95
98
|
<k-filter-item filter-keywords="show more showmore components"><a
|
|
96
99
|
href="{{pathToRoot}}components/show-more.html"
|
|
97
100
|
>Show More<br><small>Component</small></a></k-filter-item>
|
|
101
|
+
<k-filter-item filter-keywords="slider range input step components"><a
|
|
102
|
+
href="{{pathToRoot}}components/slider.html"
|
|
103
|
+
>Slider<br><small>Component</small></a></k-filter-item>
|
|
98
104
|
<k-filter-item filter-keywords="sortable drag drop sort components"><a
|
|
99
105
|
href="{{pathToRoot}}components/sortable.html"
|
|
100
106
|
>Sortable<br><small>Component</small></a></k-filter-item>
|
|
@@ -156,6 +162,9 @@
|
|
|
156
162
|
<k-filter-item filter-keywords="theme switcher dark light components"><a
|
|
157
163
|
href="{{pathToRoot}}components/theme-switcher.html"
|
|
158
164
|
>Theme Switcher<br><small>Component</small></a></k-filter-item>
|
|
165
|
+
<k-filter-item filter-keywords="time picker clock hour minute form components"><a
|
|
166
|
+
href="{{pathToRoot}}components/time.html"
|
|
167
|
+
>Time<br><small>Component</small></a></k-filter-item>
|
|
159
168
|
<k-filter-item filter-keywords="timestamp date time components"><a
|
|
160
169
|
href="{{pathToRoot}}components/timestamp.html"
|
|
161
170
|
>Timestamp<br><small>Component</small></a></k-filter-item>
|
|
@@ -250,6 +259,7 @@
|
|
|
250
259
|
<h3>Components</h3>
|
|
251
260
|
<a href="{{pathToRoot}}components/accordion.html">Accordion</a>
|
|
252
261
|
<a href="{{pathToRoot}}components/aside.html">Aside</a>
|
|
262
|
+
<a href="{{pathToRoot}}components/calendar.html">Calendar</a>
|
|
253
263
|
<a href="{{pathToRoot}}components/card.html">Card</a>
|
|
254
264
|
<a href="{{pathToRoot}}components/code-editor.html">Code Editor</a>
|
|
255
265
|
<a href="{{pathToRoot}}components/combobox.html">Combobox</a>
|
|
@@ -269,6 +279,7 @@
|
|
|
269
279
|
<a href="{{pathToRoot}}components/progress.html">Progress</a>
|
|
270
280
|
<a href="{{pathToRoot}}components/resize.html">Resize</a>
|
|
271
281
|
<a href="{{pathToRoot}}components/show-more.html">Show More</a>
|
|
282
|
+
<a href="{{pathToRoot}}components/slider.html">Slider</a>
|
|
272
283
|
<a href="{{pathToRoot}}components/sortable.html">Sortable</a>
|
|
273
284
|
<a href="{{pathToRoot}}components/spinner.html">Spinner</a>
|
|
274
285
|
<a href="{{pathToRoot}}components/split.html">Split</a>
|
|
@@ -277,6 +288,7 @@
|
|
|
277
288
|
<a href="{{pathToRoot}}components/tags.html">Tags</a>
|
|
278
289
|
<a href="{{pathToRoot}}components/theme-select.html">Theme Select</a>
|
|
279
290
|
<a href="{{pathToRoot}}components/theme-switcher.html">Theme Switcher</a>
|
|
291
|
+
<a href="{{pathToRoot}}components/time.html">Time</a>
|
|
280
292
|
<a href="{{pathToRoot}}components/timestamp.html">Timestamp</a>
|
|
281
293
|
<a href="{{pathToRoot}}components/toast.html">Toast</a>
|
|
282
294
|
<a href="{{pathToRoot}}components/toggle.html">Toggle</a>
|
package/llms.txt
CHANGED
|
@@ -54,6 +54,7 @@ import { html, css, LitElement } from 'kempo-ui/src/lit-all.min.js';
|
|
|
54
54
|
|-----------|------|-------------|------|
|
|
55
55
|
| `<k-accordion>` `<k-accordion-header>` `<k-accordion-panel>` | `Accordion.js` | Collapsible panel groups; `for-panel` attribute links headers to panels | [accordion.html](https://dustinpoissant.github.io/kempo-ui/components/accordion.html) |
|
|
56
56
|
| `<k-aside>` `<k-aside-item>` `<k-aside-label>` `<k-aside-menu>` `<k-aside-spacer>` `<k-aside-toggle>` | `Aside.js` | Sidebar/drawer; `main="push"` or `main="overlay"`, `side="left|right"` | [aside.html](https://dustinpoissant.github.io/kempo-ui/components/aside.html) |
|
|
57
|
+
| `<k-calendar>` | `Calendar.js` | Inline month-grid date picker widget; `mode="single"` (default) or `mode="range"`; `value` is ISO `YYYY-MM-DD` (single) or comma-separated pair (range); `min`/`max` bounds; form-associated; fires `change` | [calendar.html](https://dustinpoissant.github.io/kempo-ui/components/calendar.html) |
|
|
57
58
|
| `<k-card>` | `Card.js` | Bordered container; optional `label` attribute | [card.html](https://dustinpoissant.github.io/kempo-ui/components/card.html) |
|
|
58
59
|
| `<k-code-editor>` | `CodeEditor.js` | Monaco-powered code editor; `value`, `language`, `controls` attributes; form-associated; theme-aware | [code-editor.html](https://dustinpoissant.github.io/kempo-ui/components/code-editor.html) |
|
|
59
60
|
| `<k-color-picker>` | `ColorPicker.js` | Full color picker (hex, rgb, hsl, hsv, alpha); `value` property | [color-picker.html](https://dustinpoissant.github.io/kempo-ui/components/color-picker.html) |
|
|
@@ -73,6 +74,7 @@ import { html, css, LitElement } from 'kempo-ui/src/lit-all.min.js';
|
|
|
73
74
|
| `<k-photo-viewer>` | `PhotoViewer.js` | Lightbox/gallery viewer | [photo-viewer.html](https://dustinpoissant.github.io/kempo-ui/components/photo-viewer.html) |
|
|
74
75
|
| `<k-resize>` | `Resize.js` | Resizable pane with drag handle | [resize.html](https://dustinpoissant.github.io/kempo-ui/components/resize.html) |
|
|
75
76
|
| `<k-show-more>` | `ShowMore.js` | Truncated content with expand toggle | [show-more.html](https://dustinpoissant.github.io/kempo-ui/components/show-more.html) |
|
|
77
|
+
| `<k-slider>` | `Slider.js` | Slider with min, max, discrete step snap points, vertical orientation, and dual-handle range mode via comma-separated `value` (e.g. `"20,80"`); `format` attribute for value formatting (e.g. `"$0.00"`) applied to change events, form submission, and optional drag `tooltip`; form-associated; fires `change` | [slider.html](https://dustinpoissant.github.io/kempo-ui/components/slider.html) |
|
|
76
78
|
| `<k-sortable>` `<k-sortable-item>` | `Sortable.js` | Drag-to-reorder list | [sortable.html](https://dustinpoissant.github.io/kempo-ui/components/sortable.html) |
|
|
77
79
|
| `<k-spinner>` | `Spinner.js` | Loading indicator; `size` (xs/sm/md/lg/xl), `variant` (spinner/dots/bar) | [spinner.html](https://dustinpoissant.github.io/kempo-ui/components/spinner.html) |
|
|
78
80
|
| `<k-split>` | `Split.js` | Resizable split panes | [split.html](https://dustinpoissant.github.io/kempo-ui/components/split.html) |
|
|
@@ -81,6 +83,7 @@ import { html, css, LitElement } from 'kempo-ui/src/lit-all.min.js';
|
|
|
81
83
|
| `<k-tags>` `<k-tag>` | `Tags.js` | Tag input; add/remove tags; fires `change` | [tags.html](https://dustinpoissant.github.io/kempo-ui/components/tags.html) |
|
|
82
84
|
| `<k-theme-select>` | `ThemeSelect.js` | Dropdown to pick a theme | [theme-select.html](https://dustinpoissant.github.io/kempo-ui/components/theme-select.html) |
|
|
83
85
|
| `<k-theme-switcher>` | `ThemeSwitcher.js` | Theme switcher with segmented and toggle display modes | [theme-switcher.html](https://dustinpoissant.github.io/kempo-ui/components/theme-switcher.html) |
|
|
86
|
+
| `<k-time>` | `Time.js` | Wraps native `<input type="time">` with segmented hour/minute/AM-PM editing; `value` is 24-hour `HH:MM`; `increment` attr sets minute step (arrow-key granularity); `military` attr forces 24-hour display via `lang="en-GB"`; form-associated; fires `change` | [time.html](https://dustinpoissant.github.io/kempo-ui/components/time.html) |
|
|
84
87
|
| `<k-timestamp>` | `Timestamp.js` | Displays a `value` timestamp as human-readable relative time | [timestamp.html](https://dustinpoissant.github.io/kempo-ui/components/timestamp.html) |
|
|
85
88
|
| `<k-toast>` `<k-toast-container>` | `Toast.js` | Toast notifications; use `<k-toast-container>` once in page, call `.addToast(options)` | [toast.html](https://dustinpoissant.github.io/kempo-ui/components/toast.html) |
|
|
86
89
|
| `<k-toggle>` | `Toggle.js` | On/off toggle; `value` (Boolean, reflects); methods: `on()`, `off()`, `toggle()`; fires `change`, `on`, `off` | [toggle.html](https://dustinpoissant.github.io/kempo-ui/components/toggle.html) |
|
package/package.json
CHANGED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { html, css } from '../lit-all.min.js';
|
|
2
|
+
import ShadowComponent from './ShadowComponent.js';
|
|
3
|
+
|
|
4
|
+
const MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
5
|
+
const DAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
|
6
|
+
|
|
7
|
+
export default class Calendar extends ShadowComponent {
|
|
8
|
+
static formAssociated = true;
|
|
9
|
+
|
|
10
|
+
static properties = {
|
|
11
|
+
value: { type: String, reflect: true },
|
|
12
|
+
name: { type: String, reflect: true },
|
|
13
|
+
mode: { type: String, reflect: true },
|
|
14
|
+
min: { type: String, reflect: true },
|
|
15
|
+
max: { type: String, reflect: true },
|
|
16
|
+
disabled: { type: Boolean, reflect: true },
|
|
17
|
+
required: { type: Boolean, reflect: true },
|
|
18
|
+
view: { state: true },
|
|
19
|
+
hoverDate: { state: true }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
Lifecycle Callbacks
|
|
24
|
+
*/
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
this.internals = this.attachInternals();
|
|
28
|
+
this.value = '';
|
|
29
|
+
this.name = '';
|
|
30
|
+
this.mode = 'single';
|
|
31
|
+
this.min = '';
|
|
32
|
+
this.max = '';
|
|
33
|
+
this.disabled = false;
|
|
34
|
+
this.required = false;
|
|
35
|
+
const today = new Date();
|
|
36
|
+
this.view = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}`;
|
|
37
|
+
this.hoverDate = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#skipViewSync = false;
|
|
41
|
+
|
|
42
|
+
updated(changedProperties) {
|
|
43
|
+
super.updated(changedProperties);
|
|
44
|
+
this.internals.setFormValue(this.value);
|
|
45
|
+
if(this.required && !this.value){
|
|
46
|
+
this.internals.setValidity(
|
|
47
|
+
{ valueMissing: true },
|
|
48
|
+
'Please select a date.',
|
|
49
|
+
this.shadowRoot?.querySelector('.day')
|
|
50
|
+
);
|
|
51
|
+
} else {
|
|
52
|
+
this.internals.setValidity({});
|
|
53
|
+
}
|
|
54
|
+
if(changedProperties.has('value') && changedProperties.get('value') !== undefined){
|
|
55
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
56
|
+
detail: { value: this.value },
|
|
57
|
+
bubbles: true
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
if(changedProperties.has('value') && this.value && !this.#skipViewSync){
|
|
61
|
+
const first = this.fromIso(this.value.split(',')[0]);
|
|
62
|
+
if(first){
|
|
63
|
+
const newView = `${first.getFullYear()}-${String(first.getMonth() + 1).padStart(2, '0')}`;
|
|
64
|
+
if(newView !== this.view){
|
|
65
|
+
this.view = newView;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this.#skipViewSync = false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/*
|
|
73
|
+
Utility
|
|
74
|
+
*/
|
|
75
|
+
get isRange() {
|
|
76
|
+
return this.mode === 'range';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get viewYear() {
|
|
80
|
+
return Number(this.view.split('-')[0]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get viewMonth() {
|
|
84
|
+
return Number(this.view.split('-')[1]) - 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get parsed() {
|
|
88
|
+
if(!this.value){
|
|
89
|
+
return this.isRange ? { start: null, end: null } : { date: null };
|
|
90
|
+
}
|
|
91
|
+
if(this.isRange){
|
|
92
|
+
const [s, e] = this.value.split(',');
|
|
93
|
+
return {
|
|
94
|
+
start: s ? this.fromIso(s) : null,
|
|
95
|
+
end: e ? this.fromIso(e) : null
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return { date: this.fromIso(this.value) };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
toIso = (date) => {
|
|
102
|
+
if(!date) return '';
|
|
103
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
fromIso = (str) => {
|
|
107
|
+
if(!str) return null;
|
|
108
|
+
const [y, m, d] = str.split('-').map(Number);
|
|
109
|
+
if(isNaN(y) || isNaN(m) || isNaN(d)) return null;
|
|
110
|
+
return new Date(y, m - 1, d);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
isSameDay = (a, b) => {
|
|
114
|
+
if(!a || !b) return false;
|
|
115
|
+
return a.getFullYear() === b.getFullYear()
|
|
116
|
+
&& a.getMonth() === b.getMonth()
|
|
117
|
+
&& a.getDate() === b.getDate();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
isWithinRange = (date, start, end) => {
|
|
121
|
+
if(!start || !end || !date) return false;
|
|
122
|
+
const t = date.getTime();
|
|
123
|
+
return t > start.getTime() && t < end.getTime();
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
resolveDate = (input) => {
|
|
127
|
+
if(!input) return null;
|
|
128
|
+
const key = String(input).toLowerCase();
|
|
129
|
+
if(key === 'today' || key === 'tomorrow' || key === 'yesterday'){
|
|
130
|
+
const d = new Date();
|
|
131
|
+
d.setHours(0, 0, 0, 0);
|
|
132
|
+
if(key === 'tomorrow') d.setDate(d.getDate() + 1);
|
|
133
|
+
if(key === 'yesterday') d.setDate(d.getDate() - 1);
|
|
134
|
+
return d;
|
|
135
|
+
}
|
|
136
|
+
return this.fromIso(input);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
get minDate() {
|
|
140
|
+
return this.resolveDate(this.min) || this.fromIso('1900-01-01');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
get maxDate() {
|
|
144
|
+
if(this.max) return this.resolveDate(this.max);
|
|
145
|
+
const now = new Date();
|
|
146
|
+
return new Date(now.getFullYear() + 10, 11, 31);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
isDisabledDate = (date) => {
|
|
150
|
+
if(!date) return false;
|
|
151
|
+
const minDate = this.minDate;
|
|
152
|
+
if(minDate && date < minDate) return true;
|
|
153
|
+
const maxDate = this.maxDate;
|
|
154
|
+
if(maxDate && date > maxDate) return true;
|
|
155
|
+
return false;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
get availableYears() {
|
|
159
|
+
const minY = this.minDate?.getFullYear() ?? 1900;
|
|
160
|
+
const maxY = this.maxDate?.getFullYear() ?? (new Date().getFullYear() + 10);
|
|
161
|
+
const years = [];
|
|
162
|
+
for(let y = minY; y <= maxY; y++) years.push(y);
|
|
163
|
+
return years;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
isMonthDisabled = (year, month) => {
|
|
167
|
+
const lastDay = new Date(year, month + 1, 0);
|
|
168
|
+
const firstDay = new Date(year, month, 1);
|
|
169
|
+
const min = this.minDate;
|
|
170
|
+
const max = this.maxDate;
|
|
171
|
+
if(min && lastDay < min) return true;
|
|
172
|
+
if(max && firstDay > max) return true;
|
|
173
|
+
return false;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
get weekGrid() {
|
|
177
|
+
const year = this.viewYear;
|
|
178
|
+
const month = this.viewMonth;
|
|
179
|
+
const first = new Date(year, month, 1);
|
|
180
|
+
const startOffset = first.getDay();
|
|
181
|
+
const gridStart = new Date(year, month, 1 - startOffset);
|
|
182
|
+
const weeks = [];
|
|
183
|
+
for(let w = 0; w < 6; w++){
|
|
184
|
+
const days = [];
|
|
185
|
+
for(let d = 0; d < 7; d++){
|
|
186
|
+
const date = new Date(gridStart);
|
|
187
|
+
date.setDate(gridStart.getDate() + w * 7 + d);
|
|
188
|
+
days.push(date);
|
|
189
|
+
}
|
|
190
|
+
weeks.push(days);
|
|
191
|
+
}
|
|
192
|
+
return weeks;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
get monthLabel() {
|
|
196
|
+
return `${MONTH_NAMES[this.viewMonth]} ${this.viewYear}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/*
|
|
200
|
+
Navigation
|
|
201
|
+
*/
|
|
202
|
+
prevMonth = () => {
|
|
203
|
+
const d = new Date(this.viewYear, this.viewMonth - 1, 1);
|
|
204
|
+
this.view = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
nextMonth = () => {
|
|
208
|
+
const d = new Date(this.viewYear, this.viewMonth + 1, 1);
|
|
209
|
+
this.view = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
goToMonth = (year, month) => {
|
|
213
|
+
this.view = `${year}-${String(month + 1).padStart(2, '0')}`;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
goToToday = () => {
|
|
217
|
+
const today = new Date();
|
|
218
|
+
this.view = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}`;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/*
|
|
222
|
+
Event Handlers
|
|
223
|
+
*/
|
|
224
|
+
handleDayClick = (date) => {
|
|
225
|
+
if(this.disabled || this.isDisabledDate(date)) return;
|
|
226
|
+
this.#skipViewSync = true;
|
|
227
|
+
if(this.isRange){
|
|
228
|
+
const { start, end } = this.parsed;
|
|
229
|
+
if(!start || end){
|
|
230
|
+
this.value = this.toIso(date);
|
|
231
|
+
} else if(date < start){
|
|
232
|
+
this.value = `${this.toIso(date)},${this.toIso(start)}`;
|
|
233
|
+
} else {
|
|
234
|
+
this.value = `${this.toIso(start)},${this.toIso(date)}`;
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
this.value = this.toIso(date);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
handleDayHover = (date) => {
|
|
242
|
+
if(this.isRange){
|
|
243
|
+
this.hoverDate = date;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
handleMouseLeave = () => {
|
|
248
|
+
if(this.isRange){
|
|
249
|
+
this.hoverDate = null;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
handleMonthChange = (e) => {
|
|
254
|
+
this.goToMonth(this.viewYear, Number(e.target.value));
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
handleYearChange = (e) => {
|
|
258
|
+
this.goToMonth(Number(e.target.value), this.viewMonth);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/*
|
|
262
|
+
Rendering
|
|
263
|
+
*/
|
|
264
|
+
render() {
|
|
265
|
+
const today = new Date();
|
|
266
|
+
const parsed = this.parsed;
|
|
267
|
+
const single = parsed.date;
|
|
268
|
+
const rawStart = parsed.start;
|
|
269
|
+
const rawEnd = parsed.end;
|
|
270
|
+
const previewEnd = this.isRange && rawStart && !rawEnd && this.hoverDate ? this.hoverDate : rawEnd;
|
|
271
|
+
const rangeStart = this.isRange && rawStart && previewEnd && rawStart > previewEnd ? previewEnd : rawStart;
|
|
272
|
+
const rangeEnd = this.isRange && rawStart && previewEnd && rawStart > previewEnd ? rawStart : previewEnd;
|
|
273
|
+
|
|
274
|
+
return html`
|
|
275
|
+
<div class="header">
|
|
276
|
+
<select
|
|
277
|
+
class="month-select"
|
|
278
|
+
aria-label="Month"
|
|
279
|
+
?disabled=${this.disabled}
|
|
280
|
+
@change=${this.handleMonthChange}
|
|
281
|
+
>
|
|
282
|
+
${MONTH_NAMES.map((name, i) => html`
|
|
283
|
+
<option
|
|
284
|
+
value=${i}
|
|
285
|
+
?selected=${i === this.viewMonth}
|
|
286
|
+
?disabled=${this.isMonthDisabled(this.viewYear, i)}
|
|
287
|
+
>${name}</option>
|
|
288
|
+
`)}
|
|
289
|
+
</select>
|
|
290
|
+
<select
|
|
291
|
+
class="year-select"
|
|
292
|
+
aria-label="Year"
|
|
293
|
+
?disabled=${this.disabled}
|
|
294
|
+
@change=${this.handleYearChange}
|
|
295
|
+
>
|
|
296
|
+
${this.availableYears.map(y => html`
|
|
297
|
+
<option value=${y} ?selected=${y === this.viewYear}>${y}</option>
|
|
298
|
+
`)}
|
|
299
|
+
</select>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="grid" @mouseleave=${this.handleMouseLeave}>
|
|
302
|
+
<div class="row head">
|
|
303
|
+
${DAY_LABELS.map(d => html`<div class="dow">${d}</div>`)}
|
|
304
|
+
</div>
|
|
305
|
+
${this.weekGrid.map(week => html`
|
|
306
|
+
<div class="row">
|
|
307
|
+
${week.map(date => {
|
|
308
|
+
const outside = date.getMonth() !== this.viewMonth;
|
|
309
|
+
const isToday = this.isSameDay(date, today);
|
|
310
|
+
const isSelected = !this.isRange && this.isSameDay(date, single);
|
|
311
|
+
const isStart = this.isRange && this.isSameDay(date, rangeStart);
|
|
312
|
+
const isEnd = this.isRange && this.isSameDay(date, rangeEnd);
|
|
313
|
+
const inRange = this.isRange && this.isWithinRange(date, rangeStart, rangeEnd);
|
|
314
|
+
const disabled = this.isDisabledDate(date);
|
|
315
|
+
const isPreview = this.isRange && !rawEnd && (isStart || isEnd || inRange) && !isSelected;
|
|
316
|
+
const classes = [
|
|
317
|
+
'day',
|
|
318
|
+
outside ? 'outside' : '',
|
|
319
|
+
isToday ? 'today' : '',
|
|
320
|
+
isSelected ? 'selected' : '',
|
|
321
|
+
isStart ? 'range-start' : '',
|
|
322
|
+
isEnd && !isStart ? 'range-end' : '',
|
|
323
|
+
inRange ? 'in-range' : '',
|
|
324
|
+
isPreview ? 'preview' : '',
|
|
325
|
+
disabled ? 'disabled' : ''
|
|
326
|
+
].filter(Boolean).join(' ');
|
|
327
|
+
return html`
|
|
328
|
+
<button
|
|
329
|
+
type="button"
|
|
330
|
+
class="no-btn ${classes}"
|
|
331
|
+
?disabled=${disabled || this.disabled}
|
|
332
|
+
aria-selected=${isSelected || isStart || isEnd ? 'true' : 'false'}
|
|
333
|
+
@click=${() => this.handleDayClick(date)}
|
|
334
|
+
@mouseenter=${() => this.handleDayHover(date)}
|
|
335
|
+
>${date.getDate()}</button>
|
|
336
|
+
`;
|
|
337
|
+
})}
|
|
338
|
+
</div>
|
|
339
|
+
`)}
|
|
340
|
+
</div>
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/*
|
|
345
|
+
Styles
|
|
346
|
+
*/
|
|
347
|
+
static styles = css`
|
|
348
|
+
:host {
|
|
349
|
+
--day_size: 2rem;
|
|
350
|
+
--day_radius: var(--radius);
|
|
351
|
+
--day_bg__selected: var(--c_primary);
|
|
352
|
+
--day_tc__selected: white;
|
|
353
|
+
--day_bg__range: var(--c_bg__alt);
|
|
354
|
+
--day_bg__hover: var(--c_bg__alt);
|
|
355
|
+
|
|
356
|
+
display: inline-block;
|
|
357
|
+
border: 1px solid var(--c_border);
|
|
358
|
+
border-radius: var(--radius);
|
|
359
|
+
padding: 0.5rem;
|
|
360
|
+
background: var(--c_bg);
|
|
361
|
+
user-select: none;
|
|
362
|
+
}
|
|
363
|
+
:host([disabled]) {
|
|
364
|
+
opacity: 0.5;
|
|
365
|
+
pointer-events: none;
|
|
366
|
+
}
|
|
367
|
+
.header {
|
|
368
|
+
display: flex;
|
|
369
|
+
align-items: center;
|
|
370
|
+
gap: 0.25rem;
|
|
371
|
+
padding: 0.25rem;
|
|
372
|
+
margin-bottom: 0.25rem;
|
|
373
|
+
}
|
|
374
|
+
.month-select,
|
|
375
|
+
.year-select {
|
|
376
|
+
font-weight: 600;
|
|
377
|
+
width: auto;
|
|
378
|
+
}
|
|
379
|
+
.month-select {
|
|
380
|
+
flex: 1;
|
|
381
|
+
}
|
|
382
|
+
.grid {
|
|
383
|
+
display: flex;
|
|
384
|
+
flex-direction: column;
|
|
385
|
+
}
|
|
386
|
+
.row {
|
|
387
|
+
display: grid;
|
|
388
|
+
grid-template-columns: repeat(7, var(--day_size));
|
|
389
|
+
}
|
|
390
|
+
.row.head {
|
|
391
|
+
margin-bottom: 0.25rem;
|
|
392
|
+
}
|
|
393
|
+
.dow {
|
|
394
|
+
display: flex;
|
|
395
|
+
align-items: center;
|
|
396
|
+
justify-content: center;
|
|
397
|
+
font-size: 0.75rem;
|
|
398
|
+
color: var(--tc_muted);
|
|
399
|
+
font-weight: 500;
|
|
400
|
+
height: var(--day_size);
|
|
401
|
+
}
|
|
402
|
+
.day {
|
|
403
|
+
appearance: none;
|
|
404
|
+
background: transparent;
|
|
405
|
+
border: 1px solid transparent;
|
|
406
|
+
border-radius: var(--day_radius);
|
|
407
|
+
width: var(--day_size);
|
|
408
|
+
height: var(--day_size);
|
|
409
|
+
cursor: pointer;
|
|
410
|
+
color: var(--tc);
|
|
411
|
+
font: inherit;
|
|
412
|
+
font-size: 0.875rem;
|
|
413
|
+
padding: 0;
|
|
414
|
+
position: relative;
|
|
415
|
+
display: inline-flex;
|
|
416
|
+
align-items: center;
|
|
417
|
+
justify-content: center;
|
|
418
|
+
transition: background var(--animation_ms), color var(--animation_ms);
|
|
419
|
+
}
|
|
420
|
+
.day:hover:not(:disabled):not(.selected):not(.range-start):not(.range-end) {
|
|
421
|
+
background: var(--day_bg__hover);
|
|
422
|
+
}
|
|
423
|
+
.day:focus-visible {
|
|
424
|
+
outline: none;
|
|
425
|
+
border-color: var(--c_primary);
|
|
426
|
+
box-shadow: var(--focus_shadow);
|
|
427
|
+
z-index: 1;
|
|
428
|
+
}
|
|
429
|
+
.day.outside {
|
|
430
|
+
color: var(--tc_muted);
|
|
431
|
+
opacity: 0.5;
|
|
432
|
+
}
|
|
433
|
+
.day.today {
|
|
434
|
+
font-weight: 700;
|
|
435
|
+
border-color: var(--c_primary);
|
|
436
|
+
}
|
|
437
|
+
.day.selected,
|
|
438
|
+
.day.in-range,
|
|
439
|
+
.day.range-start,
|
|
440
|
+
.day.range-end {
|
|
441
|
+
background: var(--day_bg__selected);
|
|
442
|
+
color: var(--day_tc__selected);
|
|
443
|
+
border-color: transparent;
|
|
444
|
+
}
|
|
445
|
+
.day.in-range {
|
|
446
|
+
border-radius: 0;
|
|
447
|
+
}
|
|
448
|
+
.day.range-start:not(.range-end) {
|
|
449
|
+
border-top-right-radius: 0;
|
|
450
|
+
border-bottom-right-radius: 0;
|
|
451
|
+
}
|
|
452
|
+
.day.range-end:not(.range-start) {
|
|
453
|
+
border-top-left-radius: 0;
|
|
454
|
+
border-bottom-left-radius: 0;
|
|
455
|
+
}
|
|
456
|
+
.day.outside.in-range,
|
|
457
|
+
.day.outside.range-start,
|
|
458
|
+
.day.outside.range-end {
|
|
459
|
+
opacity: 0.6;
|
|
460
|
+
}
|
|
461
|
+
.day.preview.in-range,
|
|
462
|
+
.day.preview.range-start,
|
|
463
|
+
.day.preview.range-end {
|
|
464
|
+
opacity: 0.75;
|
|
465
|
+
}
|
|
466
|
+
.day:disabled {
|
|
467
|
+
opacity: 0.3;
|
|
468
|
+
cursor: not-allowed;
|
|
469
|
+
}
|
|
470
|
+
`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
customElements.define('k-calendar', Calendar);
|