@aceshooting/lyra-ui 0.1.0
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/LICENSE +21 -0
- package/README.md +98 -0
- package/dist/components/combobox/combobox.d.ts +90 -0
- package/dist/components/combobox/combobox.js +398 -0
- package/dist/components/combobox/combobox.styles.d.ts +1 -0
- package/dist/components/combobox/combobox.styles.js +148 -0
- package/dist/components/combobox/option.d.ts +32 -0
- package/dist/components/combobox/option.js +62 -0
- package/dist/components/date-picker/calendar-core.d.ts +20 -0
- package/dist/components/date-picker/calendar-core.js +71 -0
- package/dist/components/date-picker/date-input.d.ts +57 -0
- package/dist/components/date-picker/date-input.js +233 -0
- package/dist/components/date-picker/date-input.styles.d.ts +1 -0
- package/dist/components/date-picker/date-input.styles.js +67 -0
- package/dist/components/date-picker/date-picker.d.ts +64 -0
- package/dist/components/date-picker/date-picker.js +310 -0
- package/dist/components/date-picker/date-picker.styles.d.ts +1 -0
- package/dist/components/date-picker/date-picker.styles.js +101 -0
- package/dist/components/flag/flag.d.ts +35 -0
- package/dist/components/flag/flag.js +91 -0
- package/dist/components/flag/flag.styles.d.ts +1 -0
- package/dist/components/flag/flag.styles.js +27 -0
- package/dist/components/flag/language-map.d.ts +11 -0
- package/dist/components/flag/language-map.js +62 -0
- package/dist/components/sparkline/sparkline.d.ts +30 -0
- package/dist/components/sparkline/sparkline.js +74 -0
- package/dist/components/sparkline/sparkline.styles.d.ts +1 -0
- package/dist/components/sparkline/sparkline.styles.js +31 -0
- package/dist/components/toast/toast-item.d.ts +49 -0
- package/dist/components/toast/toast-item.js +123 -0
- package/dist/components/toast/toast-item.styles.d.ts +1 -0
- package/dist/components/toast/toast-item.styles.js +81 -0
- package/dist/components/toast/toast.d.ts +32 -0
- package/dist/components/toast/toast.js +48 -0
- package/dist/components/toast/toast.styles.d.ts +1 -0
- package/dist/components/toast/toast.styles.js +52 -0
- package/dist/components/toast/toaster.d.ts +26 -0
- package/dist/components/toast/toaster.js +41 -0
- package/dist/internal/a11y.d.ts +4 -0
- package/dist/internal/a11y.js +18 -0
- package/dist/internal/form-associated.d.ts +26 -0
- package/dist/internal/form-associated.js +56 -0
- package/dist/internal/lyra-element.d.ts +11 -0
- package/dist/internal/lyra-element.js +21 -0
- package/dist/internal/positioner.d.ts +10 -0
- package/dist/internal/positioner.js +21 -0
- package/dist/internal/prefix.d.ts +4 -0
- package/dist/internal/prefix.js +8 -0
- package/dist/internal/tokens.styles.d.ts +1 -0
- package/dist/internal/tokens.styles.js +35 -0
- package/dist/lyra.d.ts +26 -0
- package/dist/lyra.js +23 -0
- package/package.json +64 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
import { type WeekdayFormat } from './calendar-core.js';
|
|
4
|
+
export interface DateRange {
|
|
5
|
+
from: Date | null;
|
|
6
|
+
to: Date | null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* `<lyra-date-picker>` — an inline month-grid calendar for picking a single date
|
|
10
|
+
* or a date range. Mirrors the core `<wa-date-picker>` API under `lyra-`.
|
|
11
|
+
*
|
|
12
|
+
* Value is ISO 8601: `YYYY-MM-DD` (single) or `YYYY-MM-DD/YYYY-MM-DD` (range).
|
|
13
|
+
*
|
|
14
|
+
* @customElement lyra-date-picker
|
|
15
|
+
* @event change - The user committed a value.
|
|
16
|
+
* @event input - The value changed during interaction (range: after the first click).
|
|
17
|
+
* @csspart base, month, header, title, previous, next, weekdays, weekday, grid
|
|
18
|
+
* @csspart day, day-today, day-outside, day-selected, day-range-start, day-range-end, day-range-inner
|
|
19
|
+
*/
|
|
20
|
+
export declare class LyraDatePicker extends LyraElement {
|
|
21
|
+
static styles: import("lit").CSSResultGroup[];
|
|
22
|
+
/** ISO value: `YYYY-MM-DD` or `YYYY-MM-DD/YYYY-MM-DD`. */
|
|
23
|
+
value: string;
|
|
24
|
+
mode: 'single' | 'range';
|
|
25
|
+
min: string;
|
|
26
|
+
max: string;
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
readonly: boolean;
|
|
29
|
+
months: 1 | 2;
|
|
30
|
+
locale: string;
|
|
31
|
+
firstDayOfWeek: string;
|
|
32
|
+
weekdayFormat: WeekdayFormat;
|
|
33
|
+
disablePast: boolean;
|
|
34
|
+
disableFuture: boolean;
|
|
35
|
+
withOutsideDays: boolean;
|
|
36
|
+
private viewDate;
|
|
37
|
+
private focusedDate;
|
|
38
|
+
private focusPending;
|
|
39
|
+
get selection(): DateRange;
|
|
40
|
+
/** Read-only Date view of a single-mode value. */
|
|
41
|
+
get valueAsDate(): Date | null;
|
|
42
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
43
|
+
private get fdow();
|
|
44
|
+
private isDisabled;
|
|
45
|
+
private commit;
|
|
46
|
+
private selectDate;
|
|
47
|
+
/** Clear the selection and emit input + change. */
|
|
48
|
+
clear(): void;
|
|
49
|
+
/** Navigate to today and focus it. */
|
|
50
|
+
goToToday(): void;
|
|
51
|
+
/** Navigate the view to a date and focus it. */
|
|
52
|
+
goToDate(date: string | Date): void;
|
|
53
|
+
private nav;
|
|
54
|
+
private onGridKey;
|
|
55
|
+
protected updated(): void;
|
|
56
|
+
private renderDay;
|
|
57
|
+
private renderMonth;
|
|
58
|
+
render(): TemplateResult;
|
|
59
|
+
}
|
|
60
|
+
declare global {
|
|
61
|
+
interface HTMLElementTagNameMap {
|
|
62
|
+
'lyra-date-picker': LyraDatePicker;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { html } from 'lit';
|
|
8
|
+
import { property, state } from 'lit/decorators.js';
|
|
9
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
|
+
import { defineElement } from '../../internal/prefix.js';
|
|
11
|
+
import { styles } from './date-picker.styles.js';
|
|
12
|
+
import { monthMatrix, weekdayLabels, monthTitle, formatISO, parseISO, isSameDay, addMonths, resolveFirstDayOfWeek, } from './calendar-core.js';
|
|
13
|
+
/**
|
|
14
|
+
* `<lyra-date-picker>` — an inline month-grid calendar for picking a single date
|
|
15
|
+
* or a date range. Mirrors the core `<wa-date-picker>` API under `lyra-`.
|
|
16
|
+
*
|
|
17
|
+
* Value is ISO 8601: `YYYY-MM-DD` (single) or `YYYY-MM-DD/YYYY-MM-DD` (range).
|
|
18
|
+
*
|
|
19
|
+
* @customElement lyra-date-picker
|
|
20
|
+
* @event change - The user committed a value.
|
|
21
|
+
* @event input - The value changed during interaction (range: after the first click).
|
|
22
|
+
* @csspart base, month, header, title, previous, next, weekdays, weekday, grid
|
|
23
|
+
* @csspart day, day-today, day-outside, day-selected, day-range-start, day-range-end, day-range-inner
|
|
24
|
+
*/
|
|
25
|
+
export class LyraDatePicker extends LyraElement {
|
|
26
|
+
constructor() {
|
|
27
|
+
super(...arguments);
|
|
28
|
+
/** ISO value: `YYYY-MM-DD` or `YYYY-MM-DD/YYYY-MM-DD`. */
|
|
29
|
+
this.value = '';
|
|
30
|
+
this.mode = 'single';
|
|
31
|
+
this.min = '';
|
|
32
|
+
this.max = '';
|
|
33
|
+
this.disabled = false;
|
|
34
|
+
this.readonly = false;
|
|
35
|
+
this.months = 1;
|
|
36
|
+
this.locale = '';
|
|
37
|
+
this.firstDayOfWeek = 'auto';
|
|
38
|
+
this.weekdayFormat = 'short';
|
|
39
|
+
this.disablePast = false;
|
|
40
|
+
this.disableFuture = false;
|
|
41
|
+
this.withOutsideDays = false;
|
|
42
|
+
this.viewDate = new Date(new Date().getFullYear(), new Date().getMonth(), 1);
|
|
43
|
+
this.focusedDate = null;
|
|
44
|
+
this.focusPending = false;
|
|
45
|
+
this.onGridKey = (e) => {
|
|
46
|
+
const current = this.focusedDate ?? this.selection.from ?? new Date();
|
|
47
|
+
let next = null;
|
|
48
|
+
switch (e.key) {
|
|
49
|
+
case 'ArrowLeft':
|
|
50
|
+
next = new Date(current.getFullYear(), current.getMonth(), current.getDate() - 1);
|
|
51
|
+
break;
|
|
52
|
+
case 'ArrowRight':
|
|
53
|
+
next = new Date(current.getFullYear(), current.getMonth(), current.getDate() + 1);
|
|
54
|
+
break;
|
|
55
|
+
case 'ArrowUp':
|
|
56
|
+
next = new Date(current.getFullYear(), current.getMonth(), current.getDate() - 7);
|
|
57
|
+
break;
|
|
58
|
+
case 'ArrowDown':
|
|
59
|
+
next = new Date(current.getFullYear(), current.getMonth(), current.getDate() + 7);
|
|
60
|
+
break;
|
|
61
|
+
case 'PageUp':
|
|
62
|
+
next = new Date(current.getFullYear(), current.getMonth() - 1, current.getDate());
|
|
63
|
+
break;
|
|
64
|
+
case 'PageDown':
|
|
65
|
+
next = new Date(current.getFullYear(), current.getMonth() + 1, current.getDate());
|
|
66
|
+
break;
|
|
67
|
+
case 'Home':
|
|
68
|
+
next = new Date(current.getFullYear(), current.getMonth(), 1);
|
|
69
|
+
break;
|
|
70
|
+
case 'End':
|
|
71
|
+
next = new Date(current.getFullYear(), current.getMonth() + 1, 0);
|
|
72
|
+
break;
|
|
73
|
+
case 'Enter':
|
|
74
|
+
case ' ':
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
this.selectDate(current);
|
|
77
|
+
return;
|
|
78
|
+
default:
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
this.focusedDate = next;
|
|
83
|
+
this.viewDate = new Date(next.getFullYear(), next.getMonth(), 1);
|
|
84
|
+
this.focusPending = true;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
static { this.styles = [LyraElement.styles, styles]; }
|
|
88
|
+
get selection() {
|
|
89
|
+
const parts = this.value.split('/');
|
|
90
|
+
return { from: parseISO(parts[0] ?? ''), to: parseISO(parts[1] ?? '') };
|
|
91
|
+
}
|
|
92
|
+
/** Read-only Date view of a single-mode value. */
|
|
93
|
+
get valueAsDate() {
|
|
94
|
+
return this.mode === 'single' ? this.selection.from : null;
|
|
95
|
+
}
|
|
96
|
+
willUpdate(changed) {
|
|
97
|
+
if (changed.has('value') && this.value) {
|
|
98
|
+
const from = this.selection.from;
|
|
99
|
+
if (from)
|
|
100
|
+
this.viewDate = new Date(from.getFullYear(), from.getMonth(), 1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
get fdow() {
|
|
104
|
+
return resolveFirstDayOfWeek(this.firstDayOfWeek);
|
|
105
|
+
}
|
|
106
|
+
isDisabled(d) {
|
|
107
|
+
const min = parseISO(this.min);
|
|
108
|
+
const max = parseISO(this.max);
|
|
109
|
+
if (min && d < min)
|
|
110
|
+
return true;
|
|
111
|
+
if (max && d > max)
|
|
112
|
+
return true;
|
|
113
|
+
const today = new Date();
|
|
114
|
+
today.setHours(0, 0, 0, 0);
|
|
115
|
+
if (this.disablePast && d < today)
|
|
116
|
+
return true;
|
|
117
|
+
if (this.disableFuture && d > today)
|
|
118
|
+
return true;
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
commit(from, to, fire) {
|
|
122
|
+
this.value =
|
|
123
|
+
this.mode === 'range'
|
|
124
|
+
? from && to
|
|
125
|
+
? `${formatISO(from)}/${formatISO(to)}`
|
|
126
|
+
: from
|
|
127
|
+
? formatISO(from)
|
|
128
|
+
: ''
|
|
129
|
+
: from
|
|
130
|
+
? formatISO(from)
|
|
131
|
+
: '';
|
|
132
|
+
this.emit('input');
|
|
133
|
+
if (fire)
|
|
134
|
+
this.emit('change');
|
|
135
|
+
}
|
|
136
|
+
selectDate(date) {
|
|
137
|
+
if (this.disabled || this.readonly || this.isDisabled(date))
|
|
138
|
+
return;
|
|
139
|
+
this.focusedDate = date;
|
|
140
|
+
if (this.mode === 'range') {
|
|
141
|
+
const { from, to } = this.selection;
|
|
142
|
+
if (!from || (from && to)) {
|
|
143
|
+
this.commit(date, null, false);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
let a = from;
|
|
147
|
+
let b = date;
|
|
148
|
+
if (b < a)
|
|
149
|
+
[a, b] = [b, a];
|
|
150
|
+
this.commit(a, b, true);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
this.commit(date, null, true);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Clear the selection and emit input + change. */
|
|
158
|
+
clear() {
|
|
159
|
+
this.commit(null, null, true);
|
|
160
|
+
}
|
|
161
|
+
/** Navigate to today and focus it. */
|
|
162
|
+
goToToday() {
|
|
163
|
+
this.goToDate(new Date());
|
|
164
|
+
}
|
|
165
|
+
/** Navigate the view to a date and focus it. */
|
|
166
|
+
goToDate(date) {
|
|
167
|
+
const d = typeof date === 'string' ? parseISO(date) : date;
|
|
168
|
+
if (!d)
|
|
169
|
+
return;
|
|
170
|
+
this.viewDate = new Date(d.getFullYear(), d.getMonth(), 1);
|
|
171
|
+
this.focusedDate = d;
|
|
172
|
+
this.focusPending = true;
|
|
173
|
+
}
|
|
174
|
+
nav(delta) {
|
|
175
|
+
this.viewDate = addMonths(this.viewDate, delta);
|
|
176
|
+
}
|
|
177
|
+
updated() {
|
|
178
|
+
if (this.focusPending && this.focusedDate) {
|
|
179
|
+
this.focusPending = false;
|
|
180
|
+
const iso = formatISO(this.focusedDate);
|
|
181
|
+
const cell = this.renderRoot.querySelector(`[data-date="${iso}"]`);
|
|
182
|
+
cell?.focus();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
renderDay(date, shownMonth) {
|
|
186
|
+
const outside = date.getMonth() !== shownMonth;
|
|
187
|
+
// Outside-month days are empty placeholders by default (matches WA), keeping the
|
|
188
|
+
// 6-row grid aligned without low-contrast faded numbers.
|
|
189
|
+
if (outside && !this.withOutsideDays) {
|
|
190
|
+
return html `<span part="day-placeholder" role="gridcell"></span>`;
|
|
191
|
+
}
|
|
192
|
+
const { from, to } = this.selection;
|
|
193
|
+
const disabled = this.isDisabled(date);
|
|
194
|
+
const today = new Date();
|
|
195
|
+
const isToday = isSameDay(date, today);
|
|
196
|
+
const isStart = from && isSameDay(date, from);
|
|
197
|
+
const isEnd = to && isSameDay(date, to);
|
|
198
|
+
const selected = this.mode === 'single' ? isStart : isStart || isEnd;
|
|
199
|
+
const inRange = this.mode === 'range' && from && to && date > from && date < to;
|
|
200
|
+
const focused = this.focusedDate && isSameDay(date, this.focusedDate) ? true : !this.focusedDate && isStart;
|
|
201
|
+
const parts = ['day'];
|
|
202
|
+
if (outside)
|
|
203
|
+
parts.push('day-outside');
|
|
204
|
+
if (isToday)
|
|
205
|
+
parts.push('day-today');
|
|
206
|
+
if (selected)
|
|
207
|
+
parts.push('day-selected');
|
|
208
|
+
if (isStart && this.mode === 'range')
|
|
209
|
+
parts.push('day-range-start');
|
|
210
|
+
if (isEnd)
|
|
211
|
+
parts.push('day-range-end');
|
|
212
|
+
if (inRange)
|
|
213
|
+
parts.push('day-range-inner');
|
|
214
|
+
return html `<button
|
|
215
|
+
part=${parts.join(' ')}
|
|
216
|
+
role="gridcell"
|
|
217
|
+
data-date=${formatISO(date)}
|
|
218
|
+
aria-selected=${selected ? 'true' : 'false'}
|
|
219
|
+
aria-label=${date.toLocaleDateString(this.locale || undefined, {
|
|
220
|
+
weekday: 'long',
|
|
221
|
+
year: 'numeric',
|
|
222
|
+
month: 'long',
|
|
223
|
+
day: 'numeric',
|
|
224
|
+
})}
|
|
225
|
+
tabindex=${focused ? '0' : '-1'}
|
|
226
|
+
?disabled=${disabled}
|
|
227
|
+
@click=${() => this.selectDate(date)}
|
|
228
|
+
>
|
|
229
|
+
${date.getDate()}
|
|
230
|
+
</button>`;
|
|
231
|
+
}
|
|
232
|
+
renderMonth(offset) {
|
|
233
|
+
const base = addMonths(this.viewDate, offset);
|
|
234
|
+
const year = base.getFullYear();
|
|
235
|
+
const month = base.getMonth();
|
|
236
|
+
const matrix = monthMatrix(year, month, this.fdow);
|
|
237
|
+
const labels = weekdayLabels(this.fdow, this.weekdayFormat, this.locale);
|
|
238
|
+
const isFirst = offset === 0;
|
|
239
|
+
const isLast = offset === this.months - 1;
|
|
240
|
+
return html `<div part="month">
|
|
241
|
+
<div part="header">
|
|
242
|
+
${isFirst
|
|
243
|
+
? html `<button part="previous" type="button" aria-label="Previous month" @click=${() => this.nav(-1)}>
|
|
244
|
+
‹
|
|
245
|
+
</button>`
|
|
246
|
+
: html `<span></span>`}
|
|
247
|
+
<div part="title">${monthTitle(year, month, this.locale)}</div>
|
|
248
|
+
${isLast
|
|
249
|
+
? html `<button part="next" type="button" aria-label="Next month" @click=${() => this.nav(1)}>›</button>`
|
|
250
|
+
: html `<span></span>`}
|
|
251
|
+
</div>
|
|
252
|
+
<div part="weekdays">${labels.map((l) => html `<span part="weekday">${l}</span>`)}</div>
|
|
253
|
+
<div part="grid" role="grid" @keydown=${this.onGridKey}>
|
|
254
|
+
${matrix.map((week) => html `<div part="week" role="row">${week.map((d) => this.renderDay(d, month))}</div>`)}
|
|
255
|
+
</div>
|
|
256
|
+
</div>`;
|
|
257
|
+
}
|
|
258
|
+
render() {
|
|
259
|
+
const monthEls = [];
|
|
260
|
+
for (let i = 0; i < this.months; i++)
|
|
261
|
+
monthEls.push(this.renderMonth(i));
|
|
262
|
+
return html `<div part="base">${monthEls}</div>`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
__decorate([
|
|
266
|
+
property()
|
|
267
|
+
], LyraDatePicker.prototype, "value", void 0);
|
|
268
|
+
__decorate([
|
|
269
|
+
property()
|
|
270
|
+
], LyraDatePicker.prototype, "mode", void 0);
|
|
271
|
+
__decorate([
|
|
272
|
+
property()
|
|
273
|
+
], LyraDatePicker.prototype, "min", void 0);
|
|
274
|
+
__decorate([
|
|
275
|
+
property()
|
|
276
|
+
], LyraDatePicker.prototype, "max", void 0);
|
|
277
|
+
__decorate([
|
|
278
|
+
property({ type: Boolean, reflect: true })
|
|
279
|
+
], LyraDatePicker.prototype, "disabled", void 0);
|
|
280
|
+
__decorate([
|
|
281
|
+
property({ type: Boolean, reflect: true })
|
|
282
|
+
], LyraDatePicker.prototype, "readonly", void 0);
|
|
283
|
+
__decorate([
|
|
284
|
+
property({ type: Number })
|
|
285
|
+
], LyraDatePicker.prototype, "months", void 0);
|
|
286
|
+
__decorate([
|
|
287
|
+
property()
|
|
288
|
+
], LyraDatePicker.prototype, "locale", void 0);
|
|
289
|
+
__decorate([
|
|
290
|
+
property({ attribute: 'first-day-of-week' })
|
|
291
|
+
], LyraDatePicker.prototype, "firstDayOfWeek", void 0);
|
|
292
|
+
__decorate([
|
|
293
|
+
property({ attribute: 'weekday-format' })
|
|
294
|
+
], LyraDatePicker.prototype, "weekdayFormat", void 0);
|
|
295
|
+
__decorate([
|
|
296
|
+
property({ type: Boolean, attribute: 'disable-past' })
|
|
297
|
+
], LyraDatePicker.prototype, "disablePast", void 0);
|
|
298
|
+
__decorate([
|
|
299
|
+
property({ type: Boolean, attribute: 'disable-future' })
|
|
300
|
+
], LyraDatePicker.prototype, "disableFuture", void 0);
|
|
301
|
+
__decorate([
|
|
302
|
+
property({ type: Boolean, attribute: 'with-outside-days' })
|
|
303
|
+
], LyraDatePicker.prototype, "withOutsideDays", void 0);
|
|
304
|
+
__decorate([
|
|
305
|
+
state()
|
|
306
|
+
], LyraDatePicker.prototype, "viewDate", void 0);
|
|
307
|
+
__decorate([
|
|
308
|
+
state()
|
|
309
|
+
], LyraDatePicker.prototype, "focusedDate", void 0);
|
|
310
|
+
defineElement('date-picker', LyraDatePicker);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import("lit").CSSResult;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const styles = css `
|
|
3
|
+
:host {
|
|
4
|
+
display: inline-block;
|
|
5
|
+
--lyra-cell-size: 2.25rem;
|
|
6
|
+
}
|
|
7
|
+
[part='base'] {
|
|
8
|
+
display: flex;
|
|
9
|
+
gap: var(--lyra-space-l);
|
|
10
|
+
padding: var(--lyra-space-s);
|
|
11
|
+
background: var(--lyra-color-surface);
|
|
12
|
+
border: 1px solid var(--lyra-color-border);
|
|
13
|
+
border-radius: var(--lyra-radius);
|
|
14
|
+
}
|
|
15
|
+
[part='header'] {
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: space-between;
|
|
19
|
+
gap: var(--lyra-space-s);
|
|
20
|
+
margin-block-end: var(--lyra-space-xs);
|
|
21
|
+
}
|
|
22
|
+
[part='title'] {
|
|
23
|
+
font-weight: 600;
|
|
24
|
+
font-size: 0.9375rem;
|
|
25
|
+
}
|
|
26
|
+
[part='previous'],
|
|
27
|
+
[part='next'] {
|
|
28
|
+
border: none;
|
|
29
|
+
background: none;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
color: var(--lyra-color-text);
|
|
32
|
+
font-size: 1.1rem;
|
|
33
|
+
line-height: 1;
|
|
34
|
+
padding: var(--lyra-space-xs);
|
|
35
|
+
border-radius: var(--lyra-radius);
|
|
36
|
+
}
|
|
37
|
+
[part='previous']:hover,
|
|
38
|
+
[part='next']:hover {
|
|
39
|
+
background: var(--lyra-color-brand-quiet);
|
|
40
|
+
}
|
|
41
|
+
[part='weekdays'] {
|
|
42
|
+
display: grid;
|
|
43
|
+
grid-template-columns: repeat(7, var(--lyra-cell-size));
|
|
44
|
+
}
|
|
45
|
+
[part='weekday'] {
|
|
46
|
+
text-align: center;
|
|
47
|
+
font-size: 0.75rem;
|
|
48
|
+
color: var(--lyra-color-text-quiet);
|
|
49
|
+
padding-block: var(--lyra-space-xs);
|
|
50
|
+
}
|
|
51
|
+
[part='grid'] {
|
|
52
|
+
display: grid;
|
|
53
|
+
grid-template-columns: repeat(7, var(--lyra-cell-size));
|
|
54
|
+
}
|
|
55
|
+
[part='week'] {
|
|
56
|
+
display: contents;
|
|
57
|
+
}
|
|
58
|
+
[part~='day'] {
|
|
59
|
+
inline-size: var(--lyra-cell-size);
|
|
60
|
+
block-size: var(--lyra-cell-size);
|
|
61
|
+
border: none;
|
|
62
|
+
background: none;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
color: var(--lyra-color-text);
|
|
65
|
+
font: inherit;
|
|
66
|
+
border-radius: var(--lyra-radius);
|
|
67
|
+
}
|
|
68
|
+
[part~='day']:hover {
|
|
69
|
+
background: var(--lyra-color-brand-quiet);
|
|
70
|
+
}
|
|
71
|
+
[part~='day-outside'] {
|
|
72
|
+
color: var(--lyra-color-text-quiet);
|
|
73
|
+
}
|
|
74
|
+
[part='day-placeholder'] {
|
|
75
|
+
inline-size: var(--lyra-cell-size);
|
|
76
|
+
block-size: var(--lyra-cell-size);
|
|
77
|
+
}
|
|
78
|
+
[part~='day-today'] {
|
|
79
|
+
outline: 1px solid var(--lyra-color-brand);
|
|
80
|
+
outline-offset: -1px;
|
|
81
|
+
}
|
|
82
|
+
[part~='day-range-inner'] {
|
|
83
|
+
background: var(--lyra-color-brand-quiet);
|
|
84
|
+
border-radius: 0;
|
|
85
|
+
}
|
|
86
|
+
[part~='day-selected'],
|
|
87
|
+
[part~='day-range-start'],
|
|
88
|
+
[part~='day-range-end'] {
|
|
89
|
+
background: var(--lyra-color-brand);
|
|
90
|
+
color: #fff;
|
|
91
|
+
}
|
|
92
|
+
[part~='day']:disabled {
|
|
93
|
+
color: var(--lyra-color-text-quiet);
|
|
94
|
+
opacity: 0.35;
|
|
95
|
+
cursor: not-allowed;
|
|
96
|
+
}
|
|
97
|
+
[part~='day']:focus-visible {
|
|
98
|
+
outline: 2px solid var(--lyra-color-brand);
|
|
99
|
+
outline-offset: 1px;
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
/**
|
|
4
|
+
* `<lyra-flag>` — a country/language flag.
|
|
5
|
+
*
|
|
6
|
+
* Flag images are shipped by the optional peer package `@aceshooting/lyra-flags`,
|
|
7
|
+
* not bundled into lyra-ui itself, so importing the core library pulls zero flag
|
|
8
|
+
* weight. Give it a `country` (ISO 3166-1 alpha-2) or a `language` tag (mapped to
|
|
9
|
+
* a representative country).
|
|
10
|
+
*
|
|
11
|
+
* @customElement lyra-flag
|
|
12
|
+
* @example <lyra-flag country="fr"></lyra-flag>
|
|
13
|
+
* @example <lyra-flag language="en" label="English"></lyra-flag>
|
|
14
|
+
* @csspart image - The underlying <img>.
|
|
15
|
+
*/
|
|
16
|
+
export declare class LyraFlag extends LyraElement {
|
|
17
|
+
static styles: import("lit").CSSResultGroup[];
|
|
18
|
+
/** ISO 3166-1 alpha-2 country code (e.g. `fr`, `us`). Takes precedence over `language`. */
|
|
19
|
+
country?: string;
|
|
20
|
+
/** BCP-47-ish language tag (e.g. `en`, `en-US`) resolved to a country flag. */
|
|
21
|
+
language?: string;
|
|
22
|
+
/** Accessible label / `alt` text. Defaults to the uppercase code. */
|
|
23
|
+
label?: string;
|
|
24
|
+
/** Render as a circular flag. */
|
|
25
|
+
round: boolean;
|
|
26
|
+
private src?;
|
|
27
|
+
private get code();
|
|
28
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
29
|
+
render(): TemplateResult;
|
|
30
|
+
}
|
|
31
|
+
declare global {
|
|
32
|
+
interface HTMLElementTagNameMap {
|
|
33
|
+
'lyra-flag': LyraFlag;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { html } from 'lit';
|
|
8
|
+
import { property, state } from 'lit/decorators.js';
|
|
9
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
|
+
import { defineElement } from '../../internal/prefix.js';
|
|
11
|
+
import { styles } from './flag.styles.js';
|
|
12
|
+
import { languageToCountry } from './language-map.js';
|
|
13
|
+
let flagUrlResolver;
|
|
14
|
+
/**
|
|
15
|
+
* Lazily loads the optional peer dependency '@aceshooting/lyra-flags' once per
|
|
16
|
+
* page. Resolves to `null` (with a one-time warning) if it isn't installed.
|
|
17
|
+
*/
|
|
18
|
+
function loadFlagUrlResolver() {
|
|
19
|
+
if (!flagUrlResolver) {
|
|
20
|
+
flagUrlResolver = import('@aceshooting/lyra-flags')
|
|
21
|
+
.then((mod) => mod.flagUrl)
|
|
22
|
+
.catch(() => {
|
|
23
|
+
console.warn("<lyra-flag> needs the optional peer dependency '@aceshooting/lyra-flags' to render " +
|
|
24
|
+
'flag images — install it with `pnpm add @aceshooting/lyra-flags`.');
|
|
25
|
+
return null;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return flagUrlResolver;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `<lyra-flag>` — a country/language flag.
|
|
32
|
+
*
|
|
33
|
+
* Flag images are shipped by the optional peer package `@aceshooting/lyra-flags`,
|
|
34
|
+
* not bundled into lyra-ui itself, so importing the core library pulls zero flag
|
|
35
|
+
* weight. Give it a `country` (ISO 3166-1 alpha-2) or a `language` tag (mapped to
|
|
36
|
+
* a representative country).
|
|
37
|
+
*
|
|
38
|
+
* @customElement lyra-flag
|
|
39
|
+
* @example <lyra-flag country="fr"></lyra-flag>
|
|
40
|
+
* @example <lyra-flag language="en" label="English"></lyra-flag>
|
|
41
|
+
* @csspart image - The underlying <img>.
|
|
42
|
+
*/
|
|
43
|
+
export class LyraFlag extends LyraElement {
|
|
44
|
+
constructor() {
|
|
45
|
+
super(...arguments);
|
|
46
|
+
/** Render as a circular flag. */
|
|
47
|
+
this.round = false;
|
|
48
|
+
}
|
|
49
|
+
static { this.styles = [LyraElement.styles, styles]; }
|
|
50
|
+
get code() {
|
|
51
|
+
if (this.country)
|
|
52
|
+
return this.country.toLowerCase();
|
|
53
|
+
if (this.language)
|
|
54
|
+
return languageToCountry(this.language);
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
willUpdate(changed) {
|
|
58
|
+
if (!changed.has('country') && !changed.has('language'))
|
|
59
|
+
return;
|
|
60
|
+
const code = this.code;
|
|
61
|
+
if (!code) {
|
|
62
|
+
this.src = undefined;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
void loadFlagUrlResolver().then((resolve) => {
|
|
66
|
+
this.src = resolve?.(code);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
render() {
|
|
70
|
+
if (!this.src)
|
|
71
|
+
return html ``;
|
|
72
|
+
const alt = this.label ?? (this.code ?? '').toUpperCase();
|
|
73
|
+
return html `<img part="image" src=${this.src} alt=${alt} loading="lazy" decoding="async" />`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
__decorate([
|
|
77
|
+
property()
|
|
78
|
+
], LyraFlag.prototype, "country", void 0);
|
|
79
|
+
__decorate([
|
|
80
|
+
property()
|
|
81
|
+
], LyraFlag.prototype, "language", void 0);
|
|
82
|
+
__decorate([
|
|
83
|
+
property()
|
|
84
|
+
], LyraFlag.prototype, "label", void 0);
|
|
85
|
+
__decorate([
|
|
86
|
+
property({ type: Boolean, reflect: true })
|
|
87
|
+
], LyraFlag.prototype, "round", void 0);
|
|
88
|
+
__decorate([
|
|
89
|
+
state()
|
|
90
|
+
], LyraFlag.prototype, "src", void 0);
|
|
91
|
+
defineElement('flag', LyraFlag);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import("lit").CSSResult;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const styles = css `
|
|
3
|
+
:host {
|
|
4
|
+
display: inline-block;
|
|
5
|
+
inline-size: auto;
|
|
6
|
+
block-size: 1em;
|
|
7
|
+
line-height: 0;
|
|
8
|
+
vertical-align: middle;
|
|
9
|
+
}
|
|
10
|
+
[part='image'] {
|
|
11
|
+
display: block;
|
|
12
|
+
block-size: 100%;
|
|
13
|
+
inline-size: auto;
|
|
14
|
+
border-radius: var(--lyra-flag-radius, 2px);
|
|
15
|
+
box-shadow: 0 0 0 1px rgb(0 0 0 / 0.08) inset;
|
|
16
|
+
object-fit: cover;
|
|
17
|
+
}
|
|
18
|
+
:host([round]) {
|
|
19
|
+
block-size: 1em;
|
|
20
|
+
inline-size: 1em;
|
|
21
|
+
}
|
|
22
|
+
:host([round]) [part='image'] {
|
|
23
|
+
inline-size: 100%;
|
|
24
|
+
block-size: 100%;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default mapping from a language subtag to a representative country flag
|
|
3
|
+
* (ISO 3166-1 alpha-2). Languages don't map 1:1 to countries; these are the
|
|
4
|
+
* conventional choices for language pickers. Override per-app as needed.
|
|
5
|
+
*/
|
|
6
|
+
export declare const LANGUAGE_TO_COUNTRY: Record<string, string>;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a BCP-47-ish language tag to a flag country code.
|
|
9
|
+
* A region subtag wins (`en-US` → `us`); otherwise the base language is mapped.
|
|
10
|
+
*/
|
|
11
|
+
export declare function languageToCountry(language: string): string | undefined;
|