@aceshooting/lyra-ui 0.1.0 → 0.1.1

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.
@@ -0,0 +1,31 @@
1
+ import { type TemplateResult } from 'lit';
2
+ import { LyraElement } from '../../internal/lyra-element.js';
3
+ /**
4
+ * `<lyra-empty>` — a generic empty/no-data state. First-party invention (no
5
+ * Web Awesome equivalent); fills the gap identified across ~30 surveyed repos.
6
+ *
7
+ * @customElement lyra-empty
8
+ * @slot - Custom icon or illustration (defaults to none).
9
+ * @slot actions - Buttons/links shown below the description.
10
+ * @csspart base, icon, heading, description, actions
11
+ */
12
+ export declare class LyraEmpty extends LyraElement {
13
+ static styles: import("lit").CSSResultGroup[];
14
+ /** Short heading, e.g. "No results". */
15
+ heading: string;
16
+ /** Supporting copy, e.g. "Try a different search." */
17
+ description: string;
18
+ private hasIcon;
19
+ private hasActions;
20
+ firstUpdated(): void;
21
+ private checkIconSlot;
22
+ private checkActionsSlot;
23
+ private onIconSlotChange;
24
+ private onActionsSlotChange;
25
+ render(): TemplateResult;
26
+ }
27
+ declare global {
28
+ interface HTMLElementTagNameMap {
29
+ 'lyra-empty': LyraEmpty;
30
+ }
31
+ }
@@ -0,0 +1,78 @@
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 './empty.styles.js';
12
+ /**
13
+ * `<lyra-empty>` — a generic empty/no-data state. First-party invention (no
14
+ * Web Awesome equivalent); fills the gap identified across ~30 surveyed repos.
15
+ *
16
+ * @customElement lyra-empty
17
+ * @slot - Custom icon or illustration (defaults to none).
18
+ * @slot actions - Buttons/links shown below the description.
19
+ * @csspart base, icon, heading, description, actions
20
+ */
21
+ export class LyraEmpty extends LyraElement {
22
+ constructor() {
23
+ super(...arguments);
24
+ /** Short heading, e.g. "No results". */
25
+ this.heading = '';
26
+ /** Supporting copy, e.g. "Try a different search." */
27
+ this.description = '';
28
+ // `[part='icon']:empty` never matches because the part always contains a
29
+ // `<slot>` element (CSS `:empty` only ignores text/comment nodes). Track
30
+ // real slot assignment in JS instead and key the CSS off these instead.
31
+ this.hasIcon = false;
32
+ this.hasActions = false;
33
+ this.onIconSlotChange = (e) => {
34
+ this.checkIconSlot(e.target);
35
+ };
36
+ this.onActionsSlotChange = (e) => {
37
+ this.checkActionsSlot(e.target);
38
+ };
39
+ }
40
+ static { this.styles = [LyraElement.styles, styles]; }
41
+ firstUpdated() {
42
+ // `slotchange` isn't guaranteed to fire for content already present at
43
+ // parse/upgrade time in every browser, so do an initial check here too.
44
+ this.checkIconSlot(this.shadowRoot.querySelector('slot:not([name])'));
45
+ this.checkActionsSlot(this.shadowRoot.querySelector('slot[name="actions"]'));
46
+ }
47
+ checkIconSlot(slot) {
48
+ this.hasIcon = slot.assignedElements({ flatten: true }).length > 0;
49
+ }
50
+ checkActionsSlot(slot) {
51
+ this.hasActions = slot.assignedElements({ flatten: true }).length > 0;
52
+ }
53
+ render() {
54
+ return html `
55
+ <div part="base">
56
+ <div part="icon" ?hidden=${!this.hasIcon}><slot @slotchange=${this.onIconSlotChange}></slot></div>
57
+ <p part="heading">${this.heading}</p>
58
+ <p part="description">${this.description}</p>
59
+ <div part="actions" ?hidden=${!this.hasActions}>
60
+ <slot name="actions" @slotchange=${this.onActionsSlotChange}></slot>
61
+ </div>
62
+ </div>
63
+ `;
64
+ }
65
+ }
66
+ __decorate([
67
+ property()
68
+ ], LyraEmpty.prototype, "heading", void 0);
69
+ __decorate([
70
+ property()
71
+ ], LyraEmpty.prototype, "description", void 0);
72
+ __decorate([
73
+ state()
74
+ ], LyraEmpty.prototype, "hasIcon", void 0);
75
+ __decorate([
76
+ state()
77
+ ], LyraEmpty.prototype, "hasActions", void 0);
78
+ defineElement('empty', LyraEmpty);
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,42 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: flex;
5
+ }
6
+ [part='base'] {
7
+ display: flex;
8
+ flex-direction: column;
9
+ align-items: center;
10
+ text-align: center;
11
+ gap: var(--lyra-space-s);
12
+ padding: var(--lyra-space-l);
13
+ color: var(--lyra-color-text-quiet);
14
+ inline-size: 100%;
15
+ }
16
+ [part='icon'] {
17
+ font-size: 2rem;
18
+ line-height: 1;
19
+ color: var(--lyra-color-border);
20
+ }
21
+ [part='icon'][hidden] {
22
+ display: none;
23
+ }
24
+ [part='heading'] {
25
+ font-weight: 600;
26
+ color: var(--lyra-color-text);
27
+ margin: 0;
28
+ }
29
+ [part='description'] {
30
+ font-size: 0.875rem;
31
+ margin: 0;
32
+ }
33
+ [part='description']:empty {
34
+ display: none;
35
+ }
36
+ [part='actions'] {
37
+ margin-block-start: var(--lyra-space-s);
38
+ }
39
+ [part='actions'][hidden] {
40
+ display: none;
41
+ }
42
+ `;
@@ -0,0 +1,26 @@
1
+ import { type TemplateResult, type PropertyValues } from 'lit';
2
+ import { LyraElement } from '../../internal/lyra-element.js';
3
+ export type SkeletonVariant = 'text' | 'circle' | 'rect';
4
+ export type SkeletonEffect = 'pulse' | 'sheen';
5
+ /**
6
+ * `<lyra-skeleton>` — a loading placeholder. First-party invention; every
7
+ * surveyed repo reinvents this as a bespoke `animate-pulse` div.
8
+ *
9
+ * @customElement lyra-skeleton
10
+ * @csspart base - The placeholder shape.
11
+ */
12
+ export declare class LyraSkeleton extends LyraElement {
13
+ static styles: import("lit").CSSResultGroup[];
14
+ variant: SkeletonVariant;
15
+ effect: SkeletonEffect;
16
+ width?: string;
17
+ height?: string;
18
+ protected willUpdate(): void;
19
+ protected updated(changed: PropertyValues): void;
20
+ render(): TemplateResult;
21
+ }
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ 'lyra-skeleton': LyraSkeleton;
25
+ }
26
+ }
@@ -0,0 +1,65 @@
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 } from 'lit/decorators.js';
9
+ import { LyraElement } from '../../internal/lyra-element.js';
10
+ import { defineElement } from '../../internal/prefix.js';
11
+ import { srOnly } from '../../internal/a11y.js';
12
+ import { styles } from './skeleton.styles.js';
13
+ /**
14
+ * `<lyra-skeleton>` — a loading placeholder. First-party invention; every
15
+ * surveyed repo reinvents this as a bespoke `animate-pulse` div.
16
+ *
17
+ * @customElement lyra-skeleton
18
+ * @csspart base - The placeholder shape.
19
+ */
20
+ export class LyraSkeleton extends LyraElement {
21
+ constructor() {
22
+ super(...arguments);
23
+ this.variant = 'text';
24
+ this.effect = 'pulse';
25
+ }
26
+ static { this.styles = [LyraElement.styles, styles, srOnly]; }
27
+ willUpdate() {
28
+ this.setAttribute('role', 'status');
29
+ }
30
+ updated(changed) {
31
+ if (changed.has('width') || changed.has('height')) {
32
+ const base = this.renderRoot.querySelector('[part="base"]');
33
+ if (!base)
34
+ return;
35
+ if (this.width) {
36
+ base.style.setProperty('--lyra-skeleton-w', this.width);
37
+ }
38
+ else {
39
+ base.style.removeProperty('--lyra-skeleton-w');
40
+ }
41
+ if (this.height) {
42
+ base.style.setProperty('--lyra-skeleton-h', this.height);
43
+ }
44
+ else {
45
+ base.style.removeProperty('--lyra-skeleton-h');
46
+ }
47
+ }
48
+ }
49
+ render() {
50
+ return html `<span part="base"><span class="sr-only">Loading…</span></span>`;
51
+ }
52
+ }
53
+ __decorate([
54
+ property({ reflect: true })
55
+ ], LyraSkeleton.prototype, "variant", void 0);
56
+ __decorate([
57
+ property({ reflect: true })
58
+ ], LyraSkeleton.prototype, "effect", void 0);
59
+ __decorate([
60
+ property()
61
+ ], LyraSkeleton.prototype, "width", void 0);
62
+ __decorate([
63
+ property()
64
+ ], LyraSkeleton.prototype, "height", void 0);
65
+ defineElement('skeleton', LyraSkeleton);
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,57 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: inline-block;
5
+ inline-size: var(--lyra-skeleton-w, 100%);
6
+ block-size: var(--lyra-skeleton-h, 1em);
7
+ }
8
+ [part='base'] {
9
+ inline-size: 100%;
10
+ block-size: 100%;
11
+ background: var(--lyra-color-border);
12
+ }
13
+ :host([variant='text']) [part='base'] {
14
+ border-radius: var(--lyra-radius);
15
+ }
16
+ :host([variant='circle']) [part='base'] {
17
+ border-radius: 50%;
18
+ }
19
+ :host([variant='rect']) [part='base'] {
20
+ border-radius: var(--lyra-radius);
21
+ }
22
+ :host([effect='pulse']) [part='base'] {
23
+ animation: lyra-skeleton-pulse 1.5s ease-in-out infinite;
24
+ }
25
+ :host([effect='sheen']) [part='base'] {
26
+ background-image: linear-gradient(
27
+ 90deg,
28
+ var(--lyra-color-border) 0%,
29
+ var(--lyra-color-surface) 50%,
30
+ var(--lyra-color-border) 100%
31
+ );
32
+ background-size: 200% 100%;
33
+ animation: lyra-skeleton-sheen 1.5s ease-in-out infinite;
34
+ }
35
+ @keyframes lyra-skeleton-pulse {
36
+ 0%,
37
+ 100% {
38
+ opacity: 1;
39
+ }
40
+ 50% {
41
+ opacity: 0.4;
42
+ }
43
+ }
44
+ @keyframes lyra-skeleton-sheen {
45
+ 0% {
46
+ background-position: 200% 0;
47
+ }
48
+ 100% {
49
+ background-position: -200% 0;
50
+ }
51
+ }
52
+ @media (prefers-reduced-motion: reduce) {
53
+ [part='base'] {
54
+ animation: none !important;
55
+ }
56
+ }
57
+ `;
@@ -0,0 +1,27 @@
1
+ import { type TemplateResult } from 'lit';
2
+ import { LyraElement } from '../../internal/lyra-element.js';
3
+ export type StatVariant = 'neutral' | 'success' | 'warning' | 'danger';
4
+ /**
5
+ * `<lyra-stat>` — a KPI/stat card. First-party invention consolidating the
6
+ * "metric row" / "KPI card" pattern reinvented in every surveyed repo.
7
+ *
8
+ * @customElement lyra-stat
9
+ * @slot - Leading icon.
10
+ * @slot caption - Rich caption content (overrides the `caption` attribute).
11
+ * @csspart base, icon, label, value, unit, trend, caption
12
+ */
13
+ export declare class LyraStat extends LyraElement {
14
+ static styles: import("lit").CSSResultGroup[];
15
+ label: string;
16
+ value: string;
17
+ unit: string;
18
+ variant: StatVariant;
19
+ trend: number;
20
+ caption: string;
21
+ render(): TemplateResult;
22
+ }
23
+ declare global {
24
+ interface HTMLElementTagNameMap {
25
+ 'lyra-stat': LyraStat;
26
+ }
27
+ }
@@ -0,0 +1,72 @@
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, nothing } from 'lit';
8
+ import { property } from 'lit/decorators.js';
9
+ import { LyraElement } from '../../internal/lyra-element.js';
10
+ import { defineElement } from '../../internal/prefix.js';
11
+ import { styles } from './stat.styles.js';
12
+ /**
13
+ * `<lyra-stat>` — a KPI/stat card. First-party invention consolidating the
14
+ * "metric row" / "KPI card" pattern reinvented in every surveyed repo.
15
+ *
16
+ * @customElement lyra-stat
17
+ * @slot - Leading icon.
18
+ * @slot caption - Rich caption content (overrides the `caption` attribute).
19
+ * @csspart base, icon, label, value, unit, trend, caption
20
+ */
21
+ export class LyraStat extends LyraElement {
22
+ constructor() {
23
+ super(...arguments);
24
+ this.label = '';
25
+ this.value = '';
26
+ this.unit = '';
27
+ this.variant = 'neutral';
28
+ this.trend = NaN;
29
+ this.caption = '';
30
+ }
31
+ static { this.styles = [LyraElement.styles, styles]; }
32
+ render() {
33
+ const hasTrend = !isNaN(this.trend);
34
+ const direction = this.trend > 0 ? 'up' : this.trend < 0 ? 'down' : 'flat';
35
+ const arrow = direction === 'up' ? '▲' : direction === 'down' ? '▼' : '–';
36
+ return html `
37
+ <div part="base">
38
+ <span part="icon"><slot></slot></span>
39
+ <span part="label">${this.label}</span>
40
+ <div part="value-row">
41
+ <span part="value">${this.value}</span>
42
+ <span part="unit">${this.unit}</span>
43
+ </div>
44
+ ${hasTrend
45
+ ? html `<span part="trend" data-direction=${direction}>
46
+ ${arrow} ${Math.abs(this.trend)}%
47
+ </span>`
48
+ : nothing}
49
+ <div part="caption">${this.caption}<slot name="caption"></slot></div>
50
+ </div>
51
+ `;
52
+ }
53
+ }
54
+ __decorate([
55
+ property()
56
+ ], LyraStat.prototype, "label", void 0);
57
+ __decorate([
58
+ property()
59
+ ], LyraStat.prototype, "value", void 0);
60
+ __decorate([
61
+ property()
62
+ ], LyraStat.prototype, "unit", void 0);
63
+ __decorate([
64
+ property({ reflect: true })
65
+ ], LyraStat.prototype, "variant", void 0);
66
+ __decorate([
67
+ property({ type: Number })
68
+ ], LyraStat.prototype, "trend", void 0);
69
+ __decorate([
70
+ property()
71
+ ], LyraStat.prototype, "caption", void 0);
72
+ defineElement('stat', LyraStat);
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,76 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: block;
5
+ }
6
+ [part='base'] {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: var(--lyra-space-xs);
10
+ padding: var(--lyra-space-m);
11
+ border: 1px solid var(--lyra-color-border);
12
+ border-radius: var(--lyra-radius);
13
+ background: var(--lyra-color-surface);
14
+ }
15
+ [part='icon'] {
16
+ color: var(--lyra-color-text-quiet);
17
+ }
18
+ [part='icon']:empty {
19
+ display: none;
20
+ }
21
+ [part='label'] {
22
+ font-size: 0.75rem;
23
+ font-weight: 600;
24
+ text-transform: uppercase;
25
+ letter-spacing: 0.04em;
26
+ color: var(--lyra-color-text-quiet);
27
+ }
28
+ [part='value-row'] {
29
+ display: flex;
30
+ align-items: baseline;
31
+ gap: var(--lyra-space-xs);
32
+ }
33
+ [part='value'] {
34
+ font-size: 1.75rem;
35
+ font-weight: 700;
36
+ font-family: ui-monospace, SFMono-Regular, monospace;
37
+ }
38
+ [part='unit'] {
39
+ font-size: 0.875rem;
40
+ color: var(--lyra-color-text-quiet);
41
+ }
42
+ [part='trend'] {
43
+ display: inline-flex;
44
+ align-items: center;
45
+ gap: 0.2rem;
46
+ font-size: 0.8125rem;
47
+ font-weight: 600;
48
+ border-radius: var(--lyra-radius);
49
+ padding: 0.05rem 0.4rem;
50
+ align-self: flex-start;
51
+ }
52
+ [part='trend'][data-direction='up'] {
53
+ color: var(--lyra-color-success);
54
+ background: color-mix(in srgb, var(--lyra-color-success) 8%, transparent);
55
+ }
56
+ [part='trend'][data-direction='down'] {
57
+ color: var(--lyra-color-danger);
58
+ background: color-mix(in srgb, var(--lyra-color-danger) 8%, transparent);
59
+ }
60
+ :host([variant='success']) [part='value'] {
61
+ color: var(--lyra-color-success);
62
+ }
63
+ :host([variant='warning']) [part='value'] {
64
+ color: var(--lyra-color-warning);
65
+ }
66
+ :host([variant='danger']) [part='value'] {
67
+ color: var(--lyra-color-danger);
68
+ }
69
+ [part='caption'] {
70
+ font-size: 0.8125rem;
71
+ color: var(--lyra-color-text-quiet);
72
+ }
73
+ [part='caption']:empty {
74
+ display: none;
75
+ }
76
+ `;
@@ -0,0 +1,42 @@
1
+ import { type TemplateResult } from 'lit';
2
+ import { LyraElement } from '../../internal/lyra-element.js';
3
+ import '../empty/empty.js';
4
+ export interface TableColumn<T> {
5
+ key: string;
6
+ label: string;
7
+ sortable?: boolean;
8
+ align?: 'start' | 'end';
9
+ cell: (row: T) => unknown;
10
+ }
11
+ /**
12
+ * `<lyra-table>` — a presentational, sort/select-aware data table. The host
13
+ * owns actual sorting/filtering/pagination of `rows`; this component renders
14
+ * and emits intents (`lyra-sort`, `lyra-row-click`, `lyra-load-more`).
15
+ *
16
+ * @customElement lyra-table
17
+ * @event lyra-sort - A sortable header was activated. `detail: { key }`.
18
+ * @event lyra-row-click - A row was activated. `detail: { row }`.
19
+ * @event lyra-load-more - The "load more" control was activated.
20
+ * @csspart base, table, head, header-cell, row, cell, more-button
21
+ */
22
+ export declare class LyraTable<T = unknown> extends LyraElement {
23
+ static styles: import("lit").CSSResultGroup[];
24
+ columns: TableColumn<T>[];
25
+ rows: T[];
26
+ sortKey: string;
27
+ sortDir: 'asc' | 'desc';
28
+ rowKey?: (row: T) => string | number;
29
+ selectedKey: string | number | null;
30
+ hasMore: boolean;
31
+ moreLabel: string;
32
+ emptyHeading: string;
33
+ emptyDescription: string;
34
+ private keyOf;
35
+ private onHeaderClick;
36
+ render(): TemplateResult;
37
+ }
38
+ declare global {
39
+ interface HTMLElementTagNameMap {
40
+ 'lyra-table': LyraTable;
41
+ }
42
+ }
@@ -0,0 +1,149 @@
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, nothing } from 'lit';
8
+ import { property } from 'lit/decorators.js';
9
+ import { repeat } from 'lit/directives/repeat.js';
10
+ import { LyraElement } from '../../internal/lyra-element.js';
11
+ import { defineElement } from '../../internal/prefix.js';
12
+ import { styles } from './table.styles.js';
13
+ import '../empty/empty.js';
14
+ /**
15
+ * `<lyra-table>` — a presentational, sort/select-aware data table. The host
16
+ * owns actual sorting/filtering/pagination of `rows`; this component renders
17
+ * and emits intents (`lyra-sort`, `lyra-row-click`, `lyra-load-more`).
18
+ *
19
+ * @customElement lyra-table
20
+ * @event lyra-sort - A sortable header was activated. `detail: { key }`.
21
+ * @event lyra-row-click - A row was activated. `detail: { row }`.
22
+ * @event lyra-load-more - The "load more" control was activated.
23
+ * @csspart base, table, head, header-cell, row, cell, more-button
24
+ */
25
+ export class LyraTable extends LyraElement {
26
+ constructor() {
27
+ super(...arguments);
28
+ this.columns = [];
29
+ this.rows = [];
30
+ this.sortKey = '';
31
+ this.sortDir = 'asc';
32
+ this.selectedKey = null;
33
+ this.hasMore = false;
34
+ this.moreLabel = 'Load more';
35
+ this.emptyHeading = 'No data';
36
+ this.emptyDescription = '';
37
+ }
38
+ static { this.styles = [LyraElement.styles, styles]; }
39
+ keyOf(row, index) {
40
+ return this.rowKey ? this.rowKey(row) : index;
41
+ }
42
+ onHeaderClick(col) {
43
+ if (!col.sortable)
44
+ return;
45
+ this.emit('lyra-sort', { key: col.key });
46
+ }
47
+ render() {
48
+ if (this.rows.length === 0) {
49
+ return html `<lyra-empty
50
+ heading=${this.emptyHeading}
51
+ description=${this.emptyDescription}
52
+ ></lyra-empty>`;
53
+ }
54
+ return html `
55
+ <div part="base">
56
+ <table part="table" role="grid">
57
+ <thead part="head">
58
+ <tr role="row">
59
+ ${this.columns.map((col) => {
60
+ const ariaSort = this.sortKey === col.key ? (this.sortDir === 'asc' ? 'ascending' : 'descending') : 'none';
61
+ return html `<th
62
+ part="header-cell"
63
+ role="columnheader"
64
+ scope="col"
65
+ data-align=${col.align ?? 'start'}
66
+ ?data-sortable=${col.sortable}
67
+ aria-sort=${col.sortable ? ariaSort : nothing}
68
+ tabindex=${col.sortable ? '0' : '-1'}
69
+ @click=${() => this.onHeaderClick(col)}
70
+ @keydown=${(e) => {
71
+ if (e.key === 'Enter' || e.key === ' ') {
72
+ e.preventDefault();
73
+ this.onHeaderClick(col);
74
+ }
75
+ }}
76
+ >
77
+ ${col.label}
78
+ </th>`;
79
+ })}
80
+ </tr>
81
+ </thead>
82
+ <tbody>
83
+ ${repeat(this.rows, (row, i) => this.keyOf(row, i), (row, i) => {
84
+ const key = this.keyOf(row, i);
85
+ const selected = this.selectedKey !== null && this.selectedKey === key;
86
+ return html `<tr
87
+ part="row"
88
+ role="row"
89
+ aria-selected=${selected ? 'true' : 'false'}
90
+ tabindex="0"
91
+ @click=${() => this.emit('lyra-row-click', { row })}
92
+ @keydown=${(e) => {
93
+ if (e.key === 'Enter' || e.key === ' ') {
94
+ e.preventDefault();
95
+ this.emit('lyra-row-click', { row });
96
+ }
97
+ }}
98
+ >
99
+ ${this.columns.map((col) => html `<td part="cell" role="gridcell" data-align=${col.align ?? 'start'}>
100
+ ${col.cell(row)}
101
+ </td>`)}
102
+ </tr>`;
103
+ })}
104
+ </tbody>
105
+ </table>
106
+ ${this.hasMore
107
+ ? html `<button
108
+ part="more-button"
109
+ type="button"
110
+ @click=${() => this.emit('lyra-load-more')}
111
+ >
112
+ ${this.moreLabel}
113
+ </button>`
114
+ : nothing}
115
+ </div>
116
+ `;
117
+ }
118
+ }
119
+ __decorate([
120
+ property({ attribute: false })
121
+ ], LyraTable.prototype, "columns", void 0);
122
+ __decorate([
123
+ property({ attribute: false })
124
+ ], LyraTable.prototype, "rows", void 0);
125
+ __decorate([
126
+ property({ attribute: 'sort-key' })
127
+ ], LyraTable.prototype, "sortKey", void 0);
128
+ __decorate([
129
+ property({ attribute: 'sort-dir' })
130
+ ], LyraTable.prototype, "sortDir", void 0);
131
+ __decorate([
132
+ property({ attribute: false })
133
+ ], LyraTable.prototype, "rowKey", void 0);
134
+ __decorate([
135
+ property({ attribute: false })
136
+ ], LyraTable.prototype, "selectedKey", void 0);
137
+ __decorate([
138
+ property({ type: Boolean, attribute: 'has-more', reflect: true })
139
+ ], LyraTable.prototype, "hasMore", void 0);
140
+ __decorate([
141
+ property({ attribute: 'more-label' })
142
+ ], LyraTable.prototype, "moreLabel", void 0);
143
+ __decorate([
144
+ property({ attribute: 'empty-heading' })
145
+ ], LyraTable.prototype, "emptyHeading", void 0);
146
+ __decorate([
147
+ property({ attribute: 'empty-description' })
148
+ ], LyraTable.prototype, "emptyDescription", void 0);
149
+ defineElement('table', LyraTable);
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,66 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: block;
5
+ inline-size: 100%;
6
+ }
7
+ [part='base'] {
8
+ overflow: auto;
9
+ max-block-size: var(--lyra-table-max-height, none);
10
+ border: 1px solid var(--lyra-color-border);
11
+ border-radius: var(--lyra-radius);
12
+ }
13
+ [part='table'] {
14
+ inline-size: 100%;
15
+ border-collapse: collapse;
16
+ font-size: 0.875rem;
17
+ }
18
+ [part='header-cell'] {
19
+ position: sticky;
20
+ inset-block-start: 0;
21
+ background: var(--lyra-color-surface);
22
+ text-align: start;
23
+ font-weight: 600;
24
+ padding: var(--lyra-space-s);
25
+ border-block-end: 1px solid var(--lyra-color-border);
26
+ cursor: default;
27
+ white-space: nowrap;
28
+ }
29
+ [part='header-cell'][aria-sort]:not([aria-sort='none']),
30
+ [part='header-cell'][data-sortable] {
31
+ cursor: pointer;
32
+ }
33
+ [part='header-cell'][data-sortable]:hover {
34
+ background: var(--lyra-color-brand-quiet);
35
+ }
36
+ [part='header-cell'][data-align='end'] {
37
+ text-align: end;
38
+ }
39
+ [part='row']:hover {
40
+ background: var(--lyra-color-brand-quiet);
41
+ }
42
+ [part='row'][aria-selected='true'] {
43
+ background: var(--lyra-color-brand-quiet);
44
+ }
45
+ [part='cell'] {
46
+ padding: var(--lyra-space-s);
47
+ border-block-end: 1px solid var(--lyra-color-border);
48
+ }
49
+ [part='cell'][data-align='end'] {
50
+ text-align: end;
51
+ }
52
+ [part='more-button'] {
53
+ display: block;
54
+ inline-size: 100%;
55
+ padding: var(--lyra-space-s);
56
+ border: none;
57
+ background: none;
58
+ color: var(--lyra-color-brand);
59
+ font: inherit;
60
+ cursor: pointer;
61
+ border-block-start: 1px solid var(--lyra-color-border);
62
+ }
63
+ [part='more-button']:hover {
64
+ background: var(--lyra-color-brand-quiet);
65
+ }
66
+ `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aceshooting/lyra-ui",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Free, clean-room Lit web components — a companion to Web Awesome.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,8 +33,8 @@
33
33
  "./dist/lyra.js"
34
34
  ],
35
35
  "dependencies": {
36
- "lit": "^3.2.0",
37
- "@floating-ui/dom": "^1.6.0"
36
+ "@floating-ui/dom": "^1.7.6",
37
+ "lit": "^3.3.3"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@aceshooting/lyra-flags": "^0.1.0"
@@ -45,13 +45,13 @@
45
45
  }
46
46
  },
47
47
  "devDependencies": {
48
- "typescript": "^5.6.0",
49
- "@web/test-runner": "^0.19.0",
50
- "@web/test-runner-playwright": "^0.11.0",
51
- "@web/dev-server-esbuild": "^1.0.2",
48
+ "@aceshooting/lyra-flags": "^0.1.0",
49
+ "@custom-elements-manifest/analyzer": "^0.11.0",
52
50
  "@open-wc/testing": "^4.0.0",
53
- "@custom-elements-manifest/analyzer": "^0.10.0",
54
- "@aceshooting/lyra-flags": "0.1.0"
51
+ "@web/dev-server-esbuild": "^2.0.0",
52
+ "@web/test-runner": "^1.0.0",
53
+ "@web/test-runner-playwright": "^1.0.0",
54
+ "typescript": "^6.0.3"
55
55
  },
56
56
  "customElements": "custom-elements.json",
57
57
  "scripts": {