@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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/dist/components/combobox/combobox.d.ts +90 -0
  4. package/dist/components/combobox/combobox.js +398 -0
  5. package/dist/components/combobox/combobox.styles.d.ts +1 -0
  6. package/dist/components/combobox/combobox.styles.js +148 -0
  7. package/dist/components/combobox/option.d.ts +32 -0
  8. package/dist/components/combobox/option.js +62 -0
  9. package/dist/components/date-picker/calendar-core.d.ts +20 -0
  10. package/dist/components/date-picker/calendar-core.js +71 -0
  11. package/dist/components/date-picker/date-input.d.ts +57 -0
  12. package/dist/components/date-picker/date-input.js +233 -0
  13. package/dist/components/date-picker/date-input.styles.d.ts +1 -0
  14. package/dist/components/date-picker/date-input.styles.js +67 -0
  15. package/dist/components/date-picker/date-picker.d.ts +64 -0
  16. package/dist/components/date-picker/date-picker.js +310 -0
  17. package/dist/components/date-picker/date-picker.styles.d.ts +1 -0
  18. package/dist/components/date-picker/date-picker.styles.js +101 -0
  19. package/dist/components/flag/flag.d.ts +35 -0
  20. package/dist/components/flag/flag.js +91 -0
  21. package/dist/components/flag/flag.styles.d.ts +1 -0
  22. package/dist/components/flag/flag.styles.js +27 -0
  23. package/dist/components/flag/language-map.d.ts +11 -0
  24. package/dist/components/flag/language-map.js +62 -0
  25. package/dist/components/sparkline/sparkline.d.ts +30 -0
  26. package/dist/components/sparkline/sparkline.js +74 -0
  27. package/dist/components/sparkline/sparkline.styles.d.ts +1 -0
  28. package/dist/components/sparkline/sparkline.styles.js +31 -0
  29. package/dist/components/toast/toast-item.d.ts +49 -0
  30. package/dist/components/toast/toast-item.js +123 -0
  31. package/dist/components/toast/toast-item.styles.d.ts +1 -0
  32. package/dist/components/toast/toast-item.styles.js +81 -0
  33. package/dist/components/toast/toast.d.ts +32 -0
  34. package/dist/components/toast/toast.js +48 -0
  35. package/dist/components/toast/toast.styles.d.ts +1 -0
  36. package/dist/components/toast/toast.styles.js +52 -0
  37. package/dist/components/toast/toaster.d.ts +26 -0
  38. package/dist/components/toast/toaster.js +41 -0
  39. package/dist/internal/a11y.d.ts +4 -0
  40. package/dist/internal/a11y.js +18 -0
  41. package/dist/internal/form-associated.d.ts +26 -0
  42. package/dist/internal/form-associated.js +56 -0
  43. package/dist/internal/lyra-element.d.ts +11 -0
  44. package/dist/internal/lyra-element.js +21 -0
  45. package/dist/internal/positioner.d.ts +10 -0
  46. package/dist/internal/positioner.js +21 -0
  47. package/dist/internal/prefix.d.ts +4 -0
  48. package/dist/internal/prefix.js +8 -0
  49. package/dist/internal/tokens.styles.d.ts +1 -0
  50. package/dist/internal/tokens.styles.js +35 -0
  51. package/dist/lyra.d.ts +26 -0
  52. package/dist/lyra.js +23 -0
  53. 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;