@0001-labs/design-system-1 0.3.0-alpha.20 → 0.3.0-alpha.21
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/DS1/0-face/bento.ts +84 -0
- package/DS1/0-face/calendar.ts +2 -2
- package/DS1/1-root/bento.css +97 -0
- package/DS1/1-root/card.css +6 -1
- package/DS1/1-root/cjk.css +1 -1
- package/DS1/1-root/controls.css +1 -0
- package/DS1/1-root/fonts.css +1 -1
- package/DS1/1-root/one.css +1 -1
- package/DS1/1-root/one.semantic.css +1 -1
- package/DS1/3-unit/calendar-base.ts +31 -22
- package/DS1/3-unit/ds-bento.ts +67 -0
- package/DS1/3-unit/ds-calendar.ts +477 -22
- package/DS1/3-unit/styles/ds-bento.css +66 -0
- package/DS1/3-unit/styles/ds-calendar.css +119 -1
- package/DS1/index.ts +1 -1
- package/dist/0-face/bento.d.ts +41 -0
- package/dist/0-face/bento.d.ts.map +1 -0
- package/dist/0-face/bento.js +77 -0
- package/dist/0-face/calendar.js +2 -2
- package/dist/3-unit/calendar-base.d.ts +17 -12
- package/dist/3-unit/calendar-base.d.ts.map +1 -1
- package/dist/3-unit/calendar-base.js +21 -16
- package/dist/3-unit/ds-bento.d.ts +31 -0
- package/dist/3-unit/ds-bento.d.ts.map +1 -0
- package/dist/3-unit/ds-bento.js +47 -0
- package/dist/3-unit/ds-calendar-large.d.ts +3 -4
- package/dist/3-unit/ds-calendar-large.d.ts.map +1 -1
- package/dist/3-unit/ds-calendar.d.ts +139 -12
- package/dist/3-unit/ds-calendar.d.ts.map +1 -1
- package/dist/3-unit/ds-calendar.js +464 -23
- package/dist/design-system-1.bundle.js +1124 -1094
- package/dist/design-system-1.bundle.js.map +1 -1
- package/dist/design-system-1.bundle.min.js +2483 -2456
- package/dist/design-system-1.bundle.min.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/DS1/3-unit/ds-date-picker.ts +0 -540
- package/DS1/3-unit/styles/ds-date-picker.css +0 -124
- package/dist/3-unit/ds-date-picker.d.ts +0 -203
- package/dist/3-unit/ds-date-picker.d.ts.map +0 -1
- package/dist/3-unit/ds-date-picker.js +0 -499
|
@@ -1,499 +0,0 @@
|
|
|
1
|
-
// ds-date-picker.ts
|
|
2
|
-
// A field that opens a panel below it: the first and last day written out
|
|
3
|
-
// where they can be typed, a small month one square a day, a switch for a
|
|
4
|
-
// time, and Clear. It always collects a range: the first day pressed starts
|
|
5
|
-
// it and the second ends it; leaving after the first keeps that one day. A
|
|
6
|
-
// form takes the value as ISO 8601: a day, a day and a time, or two of them
|
|
7
|
-
// joined by a slash.
|
|
8
|
-
import { LitElement, html, nothing, unsafeCSS } from "lit";
|
|
9
|
-
import { define } from "../0-face/define";
|
|
10
|
-
import { getText } from "../0-face/i18n";
|
|
11
|
-
import { dateFormatter } from "../0-face/format";
|
|
12
|
-
import { dayOf, isDay, today as todayDay } from "../0-face/calendar";
|
|
13
|
-
import "../2-core/ds-icon";
|
|
14
|
-
import "./ds-calendar";
|
|
15
|
-
import metrics from "../1-root/component-metrics.css?inline";
|
|
16
|
-
import controls from "../1-root/controls.css?inline";
|
|
17
|
-
import styles from "./styles/ds-date-picker.css?inline";
|
|
18
|
-
const TIME = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
19
|
-
/** Reads a value: `2026-10-21`, `2026-10-21T14:30`, or two of them joined by `/`. */
|
|
20
|
-
export function readDateValue(value) {
|
|
21
|
-
const read = (part) => {
|
|
22
|
-
const [day = "", time = ""] = (part ?? "").trim().split("T");
|
|
23
|
-
if (!isDay(day))
|
|
24
|
-
return undefined;
|
|
25
|
-
const clock = time.slice(0, 5);
|
|
26
|
-
return { day, time: TIME.test(clock) ? clock : "" };
|
|
27
|
-
};
|
|
28
|
-
const [first, second] = (value ?? "").split("/");
|
|
29
|
-
let start = read(first), end = read(second);
|
|
30
|
-
if (start && end && `${end.day}T${end.time}` < `${start.day}T${start.time}`)
|
|
31
|
-
[start, end] = [end, start];
|
|
32
|
-
return { start, end };
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Writes a value from its ends, the times only with `time`. An end that is
|
|
36
|
-
* the same moment as the start leaves the value one day.
|
|
37
|
-
*/
|
|
38
|
-
export function writeDateValue(start, end, options) {
|
|
39
|
-
if (!start)
|
|
40
|
-
return "";
|
|
41
|
-
const part = (side) => (options.time && side.time ? `${side.day}T${side.time}` : side.day);
|
|
42
|
-
return end && part(end) !== part(start) ? `${part(start)}/${part(end)}` : part(start);
|
|
43
|
-
}
|
|
44
|
-
/** Reads what a person typed: a day written `YYYY-MM-DD`, or a date the browser can read ("Oct 21, 2026"). */
|
|
45
|
-
export function parseTypedDay(text) {
|
|
46
|
-
const trimmed = text.trim();
|
|
47
|
-
if (isDay(trimmed))
|
|
48
|
-
return trimmed;
|
|
49
|
-
const moment = Date.parse(trimmed);
|
|
50
|
-
return Number.isFinite(moment) ? dayOf(new Date(moment)) : null;
|
|
51
|
-
}
|
|
52
|
-
const openPickers = new Set();
|
|
53
|
-
/**
|
|
54
|
-
* @element ds-date-picker
|
|
55
|
-
* @prop {string} value - `YYYY-MM-DD`, `YYYY-MM-DDTHH:MM`, or two joined by
|
|
56
|
-
* `/` for a range; empty for none.
|
|
57
|
-
* @prop {string} name - The form field's name.
|
|
58
|
-
* @prop {string} label - The field's accessible name: a translation key, or
|
|
59
|
-
* literal words.
|
|
60
|
-
* @prop {boolean} literal - Show `label` and `placeholder` as written.
|
|
61
|
-
* @prop {string} placeholder - The words before a day is chosen; the shared
|
|
62
|
-
* "Choose a date" by default.
|
|
63
|
-
* @prop {string} length - How a day is written: `short`, `medium` (the
|
|
64
|
-
* default) or `long`.
|
|
65
|
-
* @prop {boolean} time - Choose a time with each day (the Include time switch).
|
|
66
|
-
* @prop {boolean} noOptions - Leave the Include time switch out (`no-options`);
|
|
67
|
-
* `time` still applies.
|
|
68
|
-
* @prop {string} min - The first day a person may choose.
|
|
69
|
-
* @prop {string} max - The last day a person may choose.
|
|
70
|
-
* @prop {string} weekStart - The week's first day (`week-start`); the
|
|
71
|
-
* language's own by default.
|
|
72
|
-
* @prop {string} today - The day drawn as today; the reader's own by default.
|
|
73
|
-
* @prop {boolean} required - A form needs a value; Clear is left out.
|
|
74
|
-
* @prop {boolean} disabled - The field cannot be opened.
|
|
75
|
-
* @prop {boolean} open - The panel is open.
|
|
76
|
-
*
|
|
77
|
-
* @fires change - A person changed the value; `{ value, previous }`.
|
|
78
|
-
* @fires input - The same moment, for code listening as to a field.
|
|
79
|
-
* @fires ds-date-picker-toggle - The panel opened or closed; `{ open }`.
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* <ds-date-picker label="When" literal name="when" value="2026-10-21/2026-10-24"></ds-date-picker>
|
|
83
|
-
*/
|
|
84
|
-
export class DatePicker extends LitElement {
|
|
85
|
-
constructor() {
|
|
86
|
-
super();
|
|
87
|
-
this.internals = typeof this.attachInternals === "function" ? this.attachInternals() : null;
|
|
88
|
-
/** The value the field started with, for a form's reset. */
|
|
89
|
-
this.initial = "";
|
|
90
|
-
this.shown = false;
|
|
91
|
-
/** Whether opening should move focus into the month (a key opened it). */
|
|
92
|
-
this.focusMonth = false;
|
|
93
|
-
this.relabel = () => this.requestUpdate();
|
|
94
|
-
/** Below the field, half a square from it, or above when there is more room there; held inside the screen. */
|
|
95
|
-
this.place = () => {
|
|
96
|
-
const panel = this.panel, trigger = this.trigger;
|
|
97
|
-
if (!this.shown || !panel || !trigger)
|
|
98
|
-
return;
|
|
99
|
-
const rect = trigger.getBoundingClientRect();
|
|
100
|
-
const view = this.ownerDocument.defaultView;
|
|
101
|
-
const viewport = view.visualViewport;
|
|
102
|
-
const left = viewport?.offsetLeft ?? 0, top = viewport?.offsetTop ?? 0;
|
|
103
|
-
const width = viewport?.width ?? view.innerWidth, height = viewport?.height ?? view.innerHeight;
|
|
104
|
-
const box = panel.getBoundingClientRect();
|
|
105
|
-
const gap = (parseFloat(getComputedStyle(panel).paddingTop) || 9) + 1;
|
|
106
|
-
// The face is one control high at the foot of its square: measure from that square.
|
|
107
|
-
const square = Math.max(rect.height, gap * 2);
|
|
108
|
-
const foot = rect.bottom, head = rect.bottom - square;
|
|
109
|
-
const below = top + height - foot - 2 * gap, above = head - top - 2 * gap;
|
|
110
|
-
const up = below < box.height && above > below;
|
|
111
|
-
const x = getComputedStyle(this).direction === "rtl" ? rect.right - box.width : rect.left;
|
|
112
|
-
panel.style.left = `${Math.max(left + gap, Math.min(x, left + width - gap - box.width))}px`;
|
|
113
|
-
panel.style.top = `${Math.max(top + gap, up ? head - gap - box.height : foot + gap)}px`;
|
|
114
|
-
};
|
|
115
|
-
this.onOutside = (event) => {
|
|
116
|
-
if (!event.composedPath().includes(this))
|
|
117
|
-
this.open = false;
|
|
118
|
-
};
|
|
119
|
-
this.toggle = () => {
|
|
120
|
-
if (this.disabled)
|
|
121
|
-
return;
|
|
122
|
-
this.focusMonth = false;
|
|
123
|
-
this.open = !this.open;
|
|
124
|
-
};
|
|
125
|
-
this.onTriggerKey = (event) => {
|
|
126
|
-
if (this.disabled)
|
|
127
|
-
return;
|
|
128
|
-
if (event.key === "ArrowDown" || event.key === "ArrowUp" || ((event.key === "Enter" || event.key === " ") && !this.open)) {
|
|
129
|
-
event.preventDefault();
|
|
130
|
-
this.focusMonth = true;
|
|
131
|
-
if (this.open)
|
|
132
|
-
void this.focusDay();
|
|
133
|
-
else
|
|
134
|
-
this.open = true;
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
this.onPanelKey = (event) => {
|
|
138
|
-
if (event.key === "Escape" && !event.defaultPrevented) {
|
|
139
|
-
event.preventDefault();
|
|
140
|
-
event.stopPropagation();
|
|
141
|
-
this.open = false;
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
this.onFocusOut = (event) => {
|
|
145
|
-
const next = event.relatedTarget;
|
|
146
|
-
if (this.open && next instanceof Node && !this.contains(next) && !this.renderRoot.contains(next))
|
|
147
|
-
this.open = false;
|
|
148
|
-
};
|
|
149
|
-
/**
|
|
150
|
-
* A day pressed in the month. The first press of an opening starts the range
|
|
151
|
-
* (the value is that day until another is pressed); the second ends it and,
|
|
152
|
-
* without times to set, closes the panel.
|
|
153
|
-
*/
|
|
154
|
-
this.picked = (event) => {
|
|
155
|
-
event.stopPropagation();
|
|
156
|
-
const { start, end } = this.ends;
|
|
157
|
-
const first = { day: event.detail.start, time: start?.time || (this.time ? "09:00" : "") };
|
|
158
|
-
if (!event.detail.end) {
|
|
159
|
-
this.awaitingEnd = true;
|
|
160
|
-
this.write(first, undefined);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
this.awaitingEnd = false;
|
|
164
|
-
const last = { day: event.detail.end, time: end?.time || (this.time ? "10:00" : "") };
|
|
165
|
-
this.write(first, last);
|
|
166
|
-
if (!this.time)
|
|
167
|
-
this.open = false;
|
|
168
|
-
};
|
|
169
|
-
this.setTime = () => {
|
|
170
|
-
this.time = !this.time;
|
|
171
|
-
const { start, end } = this.ends;
|
|
172
|
-
const timed = (side, clock) => (side ? { ...side, time: this.time ? side.time || clock : "" } : undefined);
|
|
173
|
-
this.write(timed(start, "09:00"), timed(end ?? (this.time ? start : undefined), "10:00"));
|
|
174
|
-
};
|
|
175
|
-
/** Pressing the switch's row presses the switch, as a label would. */
|
|
176
|
-
this.passPress = (event) => {
|
|
177
|
-
const row = event.currentTarget;
|
|
178
|
-
const control = row.querySelector(".ds-switch");
|
|
179
|
-
if (control && !event.composedPath().includes(control))
|
|
180
|
-
control.click();
|
|
181
|
-
};
|
|
182
|
-
this.clear = () => {
|
|
183
|
-
this.commit("");
|
|
184
|
-
this.open = false;
|
|
185
|
-
};
|
|
186
|
-
this.value = "";
|
|
187
|
-
this.literal = false;
|
|
188
|
-
this.time = false;
|
|
189
|
-
this.noOptions = false;
|
|
190
|
-
this.required = false;
|
|
191
|
-
this.disabled = false;
|
|
192
|
-
this.open = false;
|
|
193
|
-
this.awaitingEnd = false;
|
|
194
|
-
}
|
|
195
|
-
connectedCallback() {
|
|
196
|
-
super.connectedCallback();
|
|
197
|
-
window.addEventListener("language-changed", this.relabel);
|
|
198
|
-
window.addEventListener("translations-loaded", this.relabel);
|
|
199
|
-
}
|
|
200
|
-
disconnectedCallback() {
|
|
201
|
-
window.removeEventListener("language-changed", this.relabel);
|
|
202
|
-
window.removeEventListener("translations-loaded", this.relabel);
|
|
203
|
-
this.hide(false);
|
|
204
|
-
super.disconnectedCallback();
|
|
205
|
-
}
|
|
206
|
-
firstUpdated() {
|
|
207
|
-
this.initial = this.value;
|
|
208
|
-
}
|
|
209
|
-
/** The form was reset: back to the value it started with. */
|
|
210
|
-
formResetCallback() {
|
|
211
|
-
this.value = this.initial;
|
|
212
|
-
}
|
|
213
|
-
formDisabledCallback(disabled) {
|
|
214
|
-
this.disabled = disabled;
|
|
215
|
-
}
|
|
216
|
-
words(value, fallback) {
|
|
217
|
-
if (value && this.literal)
|
|
218
|
-
return value;
|
|
219
|
-
return getText(value || fallback) || value || fallback;
|
|
220
|
-
}
|
|
221
|
-
get trigger() {
|
|
222
|
-
return this.renderRoot.querySelector(".trigger");
|
|
223
|
-
}
|
|
224
|
-
get panel() {
|
|
225
|
-
return this.renderRoot.querySelector(".popover");
|
|
226
|
-
}
|
|
227
|
-
get calendar() {
|
|
228
|
-
return this.renderRoot.querySelector("ds-calendar");
|
|
229
|
-
}
|
|
230
|
-
get ends() {
|
|
231
|
-
return readDateValue(this.value);
|
|
232
|
-
}
|
|
233
|
-
willUpdate(changed) {
|
|
234
|
-
super.willUpdate(changed);
|
|
235
|
-
if (changed.has("value") && this.value) {
|
|
236
|
-
const { start, end } = this.ends;
|
|
237
|
-
// A value with a time turns the switch on.
|
|
238
|
-
if (!start)
|
|
239
|
-
this.value = "";
|
|
240
|
-
else if (start.time || end?.time)
|
|
241
|
-
this.time = true;
|
|
242
|
-
}
|
|
243
|
-
if (changed.has("disabled") && this.disabled)
|
|
244
|
-
this.open = false;
|
|
245
|
-
}
|
|
246
|
-
updated(changed) {
|
|
247
|
-
super.updated(changed);
|
|
248
|
-
if (changed.has("value") || changed.has("required")) {
|
|
249
|
-
this.internals?.setFormValue(this.value || null);
|
|
250
|
-
if (this.required && !this.value)
|
|
251
|
-
this.internals?.setValidity({ valueMissing: true }, this.words(undefined, "Choose a date"), this.trigger ?? undefined);
|
|
252
|
-
else
|
|
253
|
-
this.internals?.setValidity({});
|
|
254
|
-
}
|
|
255
|
-
if (changed.has("open")) {
|
|
256
|
-
if (this.open)
|
|
257
|
-
this.show();
|
|
258
|
-
else
|
|
259
|
-
this.hide(changed.get("open") === true);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
/** Opens the panel. */
|
|
263
|
-
show() {
|
|
264
|
-
if (this.shown || this.disabled || !this.isConnected)
|
|
265
|
-
return;
|
|
266
|
-
const panel = this.panel;
|
|
267
|
-
if (!panel)
|
|
268
|
-
return;
|
|
269
|
-
for (const other of openPickers)
|
|
270
|
-
if (other !== this)
|
|
271
|
-
other.open = false;
|
|
272
|
-
openPickers.add(this);
|
|
273
|
-
this.shown = true;
|
|
274
|
-
this.open = true;
|
|
275
|
-
this.awaitingEnd = false;
|
|
276
|
-
const calendar = this.calendar;
|
|
277
|
-
if (calendar)
|
|
278
|
-
calendar.month = (this.ends.start?.day || this.today || todayDay()).slice(0, 7);
|
|
279
|
-
panel.showPopover?.();
|
|
280
|
-
this.place();
|
|
281
|
-
if (calendar)
|
|
282
|
-
void calendar.updateComplete.then(this.place);
|
|
283
|
-
this.ownerDocument.addEventListener("pointerdown", this.onOutside, true);
|
|
284
|
-
this.ownerDocument.addEventListener("scroll", this.place, true);
|
|
285
|
-
window.addEventListener("resize", this.place);
|
|
286
|
-
this.dispatchEvent(new CustomEvent("ds-date-picker-toggle", { bubbles: true, composed: true, detail: { open: true } }));
|
|
287
|
-
if (this.focusMonth)
|
|
288
|
-
void this.focusDay();
|
|
289
|
-
}
|
|
290
|
-
/** Closes the panel, giving focus back to the field when it was inside. */
|
|
291
|
-
hide(restore = true) {
|
|
292
|
-
if (!this.shown)
|
|
293
|
-
return;
|
|
294
|
-
this.shown = false;
|
|
295
|
-
openPickers.delete(this);
|
|
296
|
-
this.awaitingEnd = false;
|
|
297
|
-
const panel = this.panel;
|
|
298
|
-
const within = panel?.matches(":focus-within");
|
|
299
|
-
if (panel?.matches(":popover-open"))
|
|
300
|
-
panel.hidePopover?.();
|
|
301
|
-
this.ownerDocument.removeEventListener("pointerdown", this.onOutside, true);
|
|
302
|
-
this.ownerDocument.removeEventListener("scroll", this.place, true);
|
|
303
|
-
window.removeEventListener("resize", this.place);
|
|
304
|
-
this.open = false;
|
|
305
|
-
if (restore && within)
|
|
306
|
-
this.trigger?.focus({ preventScroll: true });
|
|
307
|
-
this.dispatchEvent(new CustomEvent("ds-date-picker-toggle", { bubbles: true, composed: true, detail: { open: false } }));
|
|
308
|
-
}
|
|
309
|
-
async focusDay() {
|
|
310
|
-
const calendar = this.calendar;
|
|
311
|
-
if (!calendar)
|
|
312
|
-
return;
|
|
313
|
-
await calendar.updateComplete;
|
|
314
|
-
calendar.renderRoot.querySelector('[data-day][tabindex="0"]')?.focus({ preventScroll: true });
|
|
315
|
-
}
|
|
316
|
-
/** Takes a new value and says so. */
|
|
317
|
-
commit(value) {
|
|
318
|
-
const previous = this.value || "";
|
|
319
|
-
if (value === previous)
|
|
320
|
-
return;
|
|
321
|
-
this.value = value;
|
|
322
|
-
const detail = { value, previous };
|
|
323
|
-
this.dispatchEvent(new CustomEvent("input", { bubbles: true, composed: true, detail }));
|
|
324
|
-
this.dispatchEvent(new CustomEvent("change", { bubbles: true, composed: true, detail }));
|
|
325
|
-
}
|
|
326
|
-
write(start, end) {
|
|
327
|
-
this.commit(writeDateValue(start, end, { time: this.time }));
|
|
328
|
-
}
|
|
329
|
-
/** A day typed in a field, read when the person leaves it or presses Enter. */
|
|
330
|
-
typed(side, input) {
|
|
331
|
-
const { start, end } = this.ends;
|
|
332
|
-
const text = input.value.trim();
|
|
333
|
-
if (!text) {
|
|
334
|
-
if (side === "start")
|
|
335
|
-
this.commit("");
|
|
336
|
-
else
|
|
337
|
-
this.write(start, undefined);
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
const day = parseTypedDay(text);
|
|
341
|
-
if (!day) {
|
|
342
|
-
input.value = this.fieldText(side === "start" ? start : end);
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
const current = side === "start" ? start : end;
|
|
346
|
-
const next = { day, time: current?.time || (this.time ? (side === "start" ? "09:00" : "10:00") : "") };
|
|
347
|
-
if (side === "start")
|
|
348
|
-
this.write(next, end);
|
|
349
|
-
else
|
|
350
|
-
this.write(start ?? next, next);
|
|
351
|
-
this.awaitingEnd = false;
|
|
352
|
-
const calendar = this.calendar;
|
|
353
|
-
if (calendar)
|
|
354
|
-
calendar.month = day.slice(0, 7);
|
|
355
|
-
}
|
|
356
|
-
timed(side, input) {
|
|
357
|
-
const { start, end } = this.ends;
|
|
358
|
-
const clock = TIME.test(input.value) ? input.value : "";
|
|
359
|
-
if (side === "start" && start)
|
|
360
|
-
this.write({ ...start, time: clock }, end);
|
|
361
|
-
if (side === "end" && start)
|
|
362
|
-
this.write(start, { day: (end ?? start).day, time: clock });
|
|
363
|
-
}
|
|
364
|
-
moment(side) {
|
|
365
|
-
const [year, month, date] = side.day.split("-").map(Number);
|
|
366
|
-
const [hours, minutes] = (side.time || "12:00").split(":").map(Number);
|
|
367
|
-
return Date.UTC(year, month - 1, date, hours, minutes);
|
|
368
|
-
}
|
|
369
|
-
formatter(withTime) {
|
|
370
|
-
return dateFormatter({ dateStyle: this.length ?? "medium", ...(withTime ? { timeStyle: "short" } : {}), timeZone: "UTC" });
|
|
371
|
-
}
|
|
372
|
-
fieldText(side) {
|
|
373
|
-
return side ? this.formatter(false).format(this.moment(side)) : "";
|
|
374
|
-
}
|
|
375
|
-
/** The face's words: one day, or a range written as the language writes one ("Oct 21 – 24, 2026"). */
|
|
376
|
-
faceText() {
|
|
377
|
-
const { start, end } = this.ends;
|
|
378
|
-
if (!start)
|
|
379
|
-
return this.words(this.placeholder, "Choose a date");
|
|
380
|
-
const formatter = this.formatter(Boolean(start.time || end?.time));
|
|
381
|
-
if (!end)
|
|
382
|
-
return formatter.format(this.moment(start));
|
|
383
|
-
return formatter.formatRange ? formatter.formatRange(this.moment(start), this.moment(end)) : `${formatter.format(this.moment(start))} – ${formatter.format(this.moment(end))}`;
|
|
384
|
-
}
|
|
385
|
-
renderEnd(side, value, name) {
|
|
386
|
-
const label = side === "start" ? name : `${name}, ${this.words(undefined, "End date")}`;
|
|
387
|
-
return html `<div class="field-row" part="field">
|
|
388
|
-
<input
|
|
389
|
-
class="ds-input field"
|
|
390
|
-
type="text"
|
|
391
|
-
autocomplete="off"
|
|
392
|
-
aria-label=${label}
|
|
393
|
-
placeholder=${side === "start" ? this.words(this.placeholder, "Choose a date") : this.words(undefined, "End date")}
|
|
394
|
-
.value=${this.fieldText(value)}
|
|
395
|
-
@change=${(event) => this.typed(side, event.target)}
|
|
396
|
-
@keydown=${(event) => { if (event.key === "Enter") {
|
|
397
|
-
event.preventDefault();
|
|
398
|
-
this.typed(side, event.target);
|
|
399
|
-
} }}
|
|
400
|
-
/>
|
|
401
|
-
</div>
|
|
402
|
-
${this.time
|
|
403
|
-
? html `<div class="field-row" part="field">
|
|
404
|
-
<input
|
|
405
|
-
class="ds-input clock"
|
|
406
|
-
type="time"
|
|
407
|
-
aria-label=${`${label}, ${this.words(undefined, "Time")}`}
|
|
408
|
-
.value=${value?.time ?? ""}
|
|
409
|
-
?disabled=${!this.ends.start}
|
|
410
|
-
@change=${(event) => this.timed(side, event.target)}
|
|
411
|
-
/>
|
|
412
|
-
</div>`
|
|
413
|
-
: nothing}`;
|
|
414
|
-
}
|
|
415
|
-
render() {
|
|
416
|
-
const { start, end } = this.ends;
|
|
417
|
-
const text = this.faceText();
|
|
418
|
-
const name = this.label ? this.words(this.label, this.label) : this.words(undefined, "Date");
|
|
419
|
-
// While the end is still to come the month holds the start alone; otherwise the range is whole (one day is a range of one).
|
|
420
|
-
const days = start ? (this.awaitingEnd ? start.day : `${start.day}/${(end ?? start).day}`) : "";
|
|
421
|
-
return html `<button
|
|
422
|
-
class="ds-select trigger"
|
|
423
|
-
part="trigger"
|
|
424
|
-
type="button"
|
|
425
|
-
aria-haspopup="dialog"
|
|
426
|
-
aria-expanded=${String(this.open)}
|
|
427
|
-
aria-label=${start ? `${name}, ${text}` : name}
|
|
428
|
-
?disabled=${this.disabled}
|
|
429
|
-
?data-empty=${!start}
|
|
430
|
-
@click=${this.toggle}
|
|
431
|
-
@keydown=${this.onTriggerKey}
|
|
432
|
-
>
|
|
433
|
-
<ds-icon class="icon" type="calendar" aria-hidden="true"></ds-icon>
|
|
434
|
-
<span class="text">${text}</span>
|
|
435
|
-
<ds-icon class="arrow" type="down" aria-hidden="true"></ds-icon>
|
|
436
|
-
</button>
|
|
437
|
-
<div
|
|
438
|
-
class="popover"
|
|
439
|
-
part="popover"
|
|
440
|
-
popover="manual"
|
|
441
|
-
role="dialog"
|
|
442
|
-
aria-label=${name}
|
|
443
|
-
@keydown=${this.onPanelKey}
|
|
444
|
-
@focusout=${this.onFocusOut}
|
|
445
|
-
>
|
|
446
|
-
<div class="fields">
|
|
447
|
-
${this.renderEnd("start", start, name)}
|
|
448
|
-
${this.renderEnd("end", start ? end ?? start : undefined, name)}
|
|
449
|
-
</div>
|
|
450
|
-
<ds-calendar
|
|
451
|
-
compact
|
|
452
|
-
selectable
|
|
453
|
-
range
|
|
454
|
-
fixed
|
|
455
|
-
outside-days
|
|
456
|
-
.value=${days}
|
|
457
|
-
.min=${this.min}
|
|
458
|
-
.max=${this.max}
|
|
459
|
-
.weekStart=${this.weekStart}
|
|
460
|
-
.today=${this.today}
|
|
461
|
-
@change=${this.picked}
|
|
462
|
-
></ds-calendar>
|
|
463
|
-
<div class="options" part="options">
|
|
464
|
-
${this.noOptions
|
|
465
|
-
? nothing
|
|
466
|
-
: html `<div class="separator" aria-hidden="true"></div>
|
|
467
|
-
<div class="option" @click=${this.passPress}>
|
|
468
|
-
<span>${this.words(undefined, "Include time")}</span>
|
|
469
|
-
<button class="ds-switch" type="button" role="switch" aria-label=${this.words(undefined, "Include time")} aria-checked=${String(this.time)} @click=${this.setTime}></button>
|
|
470
|
-
</div>`}
|
|
471
|
-
${this.required
|
|
472
|
-
? nothing
|
|
473
|
-
: html `<div class="separator" aria-hidden="true"></div>
|
|
474
|
-
<button class="option clear" part="clear" type="button" ?disabled=${!start} @click=${this.clear}>${this.words(undefined, "Clear")}</button>`}
|
|
475
|
-
</div>
|
|
476
|
-
</div>`;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
DatePicker.formAssociated = true;
|
|
480
|
-
DatePicker.properties = {
|
|
481
|
-
value: { type: String, reflect: true },
|
|
482
|
-
name: { type: String, reflect: true },
|
|
483
|
-
label: { type: String },
|
|
484
|
-
literal: { type: Boolean, reflect: true },
|
|
485
|
-
placeholder: { type: String },
|
|
486
|
-
length: { type: String },
|
|
487
|
-
time: { type: Boolean, reflect: true },
|
|
488
|
-
noOptions: { type: Boolean, attribute: "no-options", reflect: true },
|
|
489
|
-
min: { type: String },
|
|
490
|
-
max: { type: String },
|
|
491
|
-
weekStart: { type: String, attribute: "week-start" },
|
|
492
|
-
today: { type: String },
|
|
493
|
-
required: { type: Boolean, reflect: true },
|
|
494
|
-
disabled: { type: Boolean, reflect: true },
|
|
495
|
-
open: { type: Boolean, reflect: true },
|
|
496
|
-
awaitingEnd: { state: true },
|
|
497
|
-
};
|
|
498
|
-
DatePicker.styles = [unsafeCSS(metrics), unsafeCSS(controls), unsafeCSS(styles)];
|
|
499
|
-
define("ds-date-picker", DatePicker);
|