@xmesh/system-design 0.0.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.
Files changed (175) hide show
  1. package/README.md +472 -0
  2. package/assets/brand-lockup-dark.svg +9 -0
  3. package/assets/brand-lockup-light.svg +9 -0
  4. package/assets/brand-mark.svg +9 -0
  5. package/colors_and_type.css +11 -0
  6. package/dist/lit/components/alert/index.css +201 -0
  7. package/dist/lit/components/alert/index.d.ts +25 -0
  8. package/dist/lit/components/alert/index.js +191 -0
  9. package/dist/lit/components/app-bar/index.css +80 -0
  10. package/dist/lit/components/app-bar/index.d.ts +19 -0
  11. package/dist/lit/components/app-bar/index.js +120 -0
  12. package/dist/lit/components/artifact/index.css +166 -0
  13. package/dist/lit/components/artifact/index.d.ts +37 -0
  14. package/dist/lit/components/artifact/index.js +294 -0
  15. package/dist/lit/components/autocomplete/index.css +171 -0
  16. package/dist/lit/components/autocomplete/index.d.ts +47 -0
  17. package/dist/lit/components/autocomplete/index.js +404 -0
  18. package/dist/lit/components/avatar/index.css +62 -0
  19. package/dist/lit/components/avatar/index.d.ts +19 -0
  20. package/dist/lit/components/avatar/index.js +112 -0
  21. package/dist/lit/components/avatar-group/index.css +60 -0
  22. package/dist/lit/components/avatar-group/index.d.ts +19 -0
  23. package/dist/lit/components/avatar-group/index.js +97 -0
  24. package/dist/lit/components/badge/index.css +72 -0
  25. package/dist/lit/components/badge/index.d.ts +18 -0
  26. package/dist/lit/components/badge/index.js +115 -0
  27. package/dist/lit/components/brand-mark/index.css +109 -0
  28. package/dist/lit/components/brand-mark/index.d.ts +24 -0
  29. package/dist/lit/components/brand-mark/index.js +116 -0
  30. package/dist/lit/components/breadcrumbs/index.css +91 -0
  31. package/dist/lit/components/breadcrumbs/index.d.ts +19 -0
  32. package/dist/lit/components/breadcrumbs/index.js +104 -0
  33. package/dist/lit/components/bubble/index.css +182 -0
  34. package/dist/lit/components/bubble/index.d.ts +72 -0
  35. package/dist/lit/components/bubble/index.js +617 -0
  36. package/dist/lit/components/button/index.css +342 -0
  37. package/dist/lit/components/button/index.d.ts +32 -0
  38. package/dist/lit/components/button/index.js +202 -0
  39. package/dist/lit/components/card/index.css +99 -0
  40. package/dist/lit/components/card/index.d.ts +20 -0
  41. package/dist/lit/components/card/index.js +133 -0
  42. package/dist/lit/components/chat/index.css +292 -0
  43. package/dist/lit/components/chat/index.d.ts +74 -0
  44. package/dist/lit/components/chat/index.js +589 -0
  45. package/dist/lit/components/checkbox/index.css +126 -0
  46. package/dist/lit/components/checkbox/index.d.ts +21 -0
  47. package/dist/lit/components/checkbox/index.js +138 -0
  48. package/dist/lit/components/chip/index.css +145 -0
  49. package/dist/lit/components/chip/index.d.ts +30 -0
  50. package/dist/lit/components/chip/index.js +230 -0
  51. package/dist/lit/components/chip-group/index.css +19 -0
  52. package/dist/lit/components/chip-group/index.d.ts +24 -0
  53. package/dist/lit/components/chip-group/index.js +171 -0
  54. package/dist/lit/components/code/index.css +42 -0
  55. package/dist/lit/components/code/index.d.ts +12 -0
  56. package/dist/lit/components/code/index.js +68 -0
  57. package/dist/lit/components/composer/index.css +548 -0
  58. package/dist/lit/components/composer/index.d.ts +67 -0
  59. package/dist/lit/components/composer/index.js +713 -0
  60. package/dist/lit/components/data-table/index.css +166 -0
  61. package/dist/lit/components/data-table/index.d.ts +55 -0
  62. package/dist/lit/components/data-table/index.js +390 -0
  63. package/dist/lit/components/dialog/index.css +124 -0
  64. package/dist/lit/components/dialog/index.d.ts +24 -0
  65. package/dist/lit/components/dialog/index.js +199 -0
  66. package/dist/lit/components/divider/index.css +27 -0
  67. package/dist/lit/components/divider/index.d.ts +13 -0
  68. package/dist/lit/components/divider/index.js +67 -0
  69. package/dist/lit/components/empty-state/index.css +69 -0
  70. package/dist/lit/components/empty-state/index.d.ts +21 -0
  71. package/dist/lit/components/empty-state/index.js +123 -0
  72. package/dist/lit/components/expansion-panel/index.css +120 -0
  73. package/dist/lit/components/expansion-panel/index.d.ts +22 -0
  74. package/dist/lit/components/expansion-panel/index.js +174 -0
  75. package/dist/lit/components/field/index.css +223 -0
  76. package/dist/lit/components/field/index.d.ts +106 -0
  77. package/dist/lit/components/field/index.js +388 -0
  78. package/dist/lit/components/file-input/index.css +257 -0
  79. package/dist/lit/components/file-input/index.d.ts +30 -0
  80. package/dist/lit/components/file-input/index.js +298 -0
  81. package/dist/lit/components/form/index.css +29 -0
  82. package/dist/lit/components/form/index.d.ts +38 -0
  83. package/dist/lit/components/form/index.js +192 -0
  84. package/dist/lit/components/grid/index.css +53 -0
  85. package/dist/lit/components/grid/index.d.ts +14 -0
  86. package/dist/lit/components/grid/index.js +82 -0
  87. package/dist/lit/components/kbd/index.css +35 -0
  88. package/dist/lit/components/kbd/index.d.ts +11 -0
  89. package/dist/lit/components/kbd/index.js +43 -0
  90. package/dist/lit/components/list/index.css +15 -0
  91. package/dist/lit/components/list/index.d.ts +28 -0
  92. package/dist/lit/components/list/index.js +188 -0
  93. package/dist/lit/components/list-item/index.css +119 -0
  94. package/dist/lit/components/list-item/index.d.ts +20 -0
  95. package/dist/lit/components/list-item/index.js +127 -0
  96. package/dist/lit/components/menu/index.css +94 -0
  97. package/dist/lit/components/menu/index.d.ts +47 -0
  98. package/dist/lit/components/menu/index.js +386 -0
  99. package/dist/lit/components/navigation-drawer/index.css +114 -0
  100. package/dist/lit/components/navigation-drawer/index.d.ts +29 -0
  101. package/dist/lit/components/navigation-drawer/index.js +218 -0
  102. package/dist/lit/components/overlay/index.css +171 -0
  103. package/dist/lit/components/overlay/index.d.ts +65 -0
  104. package/dist/lit/components/overlay/index.js +566 -0
  105. package/dist/lit/components/pagination/index.css +102 -0
  106. package/dist/lit/components/pagination/index.d.ts +22 -0
  107. package/dist/lit/components/pagination/index.js +184 -0
  108. package/dist/lit/components/primitives/index.css +504 -0
  109. package/dist/lit/components/primitives/index.d.ts +25 -0
  110. package/dist/lit/components/primitives/index.js +283 -0
  111. package/dist/lit/components/progress/index.css +143 -0
  112. package/dist/lit/components/progress/index.d.ts +23 -0
  113. package/dist/lit/components/progress/index.js +180 -0
  114. package/dist/lit/components/radio-group/index.css +178 -0
  115. package/dist/lit/components/radio-group/index.d.ts +35 -0
  116. package/dist/lit/components/radio-group/index.js +292 -0
  117. package/dist/lit/components/select/index.css +151 -0
  118. package/dist/lit/components/select/index.d.ts +50 -0
  119. package/dist/lit/components/select/index.js +390 -0
  120. package/dist/lit/components/sidebar-item/index.css +133 -0
  121. package/dist/lit/components/sidebar-item/index.d.ts +20 -0
  122. package/dist/lit/components/sidebar-item/index.js +105 -0
  123. package/dist/lit/components/skeleton/index.css +81 -0
  124. package/dist/lit/components/skeleton/index.d.ts +19 -0
  125. package/dist/lit/components/skeleton/index.js +119 -0
  126. package/dist/lit/components/slider/index.css +171 -0
  127. package/dist/lit/components/slider/index.d.ts +36 -0
  128. package/dist/lit/components/slider/index.js +302 -0
  129. package/dist/lit/components/snackbar/index.css +279 -0
  130. package/dist/lit/components/snackbar/index.d.ts +33 -0
  131. package/dist/lit/components/snackbar/index.js +195 -0
  132. package/dist/lit/components/stack/index.css +41 -0
  133. package/dist/lit/components/stack/index.d.ts +20 -0
  134. package/dist/lit/components/stack/index.js +103 -0
  135. package/dist/lit/components/switch/index.css +126 -0
  136. package/dist/lit/components/switch/index.d.ts +17 -0
  137. package/dist/lit/components/switch/index.js +116 -0
  138. package/dist/lit/components/table/index.css +85 -0
  139. package/dist/lit/components/table/index.d.ts +25 -0
  140. package/dist/lit/components/table/index.js +139 -0
  141. package/dist/lit/components/tabs/index.css +116 -0
  142. package/dist/lit/components/tabs/index.d.ts +49 -0
  143. package/dist/lit/components/tabs/index.js +320 -0
  144. package/dist/lit/components/text-field/index.css +90 -0
  145. package/dist/lit/components/text-field/index.d.ts +17 -0
  146. package/dist/lit/components/text-field/index.js +101 -0
  147. package/dist/lit/components/textarea/index.css +55 -0
  148. package/dist/lit/components/textarea/index.d.ts +26 -0
  149. package/dist/lit/components/textarea/index.js +124 -0
  150. package/dist/lit/components/tooltip/index.css +37 -0
  151. package/dist/lit/components/tooltip/index.d.ts +31 -0
  152. package/dist/lit/components/tooltip/index.js +196 -0
  153. package/dist/lit/components/validation/index.css +386 -0
  154. package/dist/lit/components/validation/index.d.ts +45 -0
  155. package/dist/lit/components/validation/index.js +318 -0
  156. package/dist/lit/index.d.ts +50 -0
  157. package/dist/lit/index.js +59 -0
  158. package/package.json +81 -0
  159. package/styles/README.md +346 -0
  160. package/styles/_elevation.css +24 -0
  161. package/styles/_fonts.css +6 -0
  162. package/styles/_layout.css +37 -0
  163. package/styles/_primitives.css +154 -0
  164. package/styles/_scroll.css +75 -0
  165. package/styles/_semantic.css +146 -0
  166. package/styles/_space.css +61 -0
  167. package/styles/_type.css +139 -0
  168. package/styles/_xmesh-extensions.css +232 -0
  169. package/styles/index.css +44 -0
  170. package/styles/md3/_color.css +102 -0
  171. package/styles/md3/_elevation.css +26 -0
  172. package/styles/md3/_motion.css +35 -0
  173. package/styles/md3/_shape.css +22 -0
  174. package/styles/md3/_state.css +22 -0
  175. package/styles/md3/_type.css +111 -0
@@ -0,0 +1,82 @@
1
+ /*
2
+ grid/index.ts — <xm-grid>, the column grid layout primitive.
3
+
4
+ <xm-grid> — an N-column (default 12) CSS grid. Slotted children are grid
5
+ items: the <slot> is layout-transparent, so a child lays out as if it were a
6
+ direct child of the grid container (same mechanism as xm-chip-group's flex).
7
+
8
+ A child spans columns by setting the inline custom property --xm-col-span on
9
+ ITSELF (default 1). The grid reads it via ::slotted(*) — zero JS, arbitrary N,
10
+ shadow-safe (custom properties inherit across the shadow boundary). Optional
11
+ --xm-col-start places a child explicitly.
12
+
13
+ Authoring:
14
+ <xm-grid columns="12" gap="md">
15
+ <div style="--xm-col-span: 8">main</div>
16
+ <div style="--xm-col-span: 4">aside</div>
17
+ <div>defaults to span 1</div>
18
+ </xm-grid>
19
+
20
+ Properties:
21
+ columns number of equal tracks (default 12, clamped ≥ 1)
22
+ gap "none" | "xs" | "sm" | "md" | "lg" | "xl" (default "md")
23
+ → --xm-gutter-* (aliases the --s-N scale)
24
+
25
+ Below --xm-breakpoint-sm (520px) the grid collapses to a single column — see
26
+ index.css (the literal is repeated there because @media can't read var();
27
+ docs/adr/0001).
28
+
29
+ Shadow DOM; Lit from lit; sibling CSS via the
30
+ built-file-relative new URL(...). BEM block `grid`.
31
+ */
32
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
33
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
34
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
35
+ 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;
36
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
37
+ };
38
+ import { LitElement, html } from "lit";
39
+ import { customElement, property } from "lit/decorators.js";
40
+ // Resolve CSS relative to the *built* file:
41
+ // lit/build/components/grid/index.js → ../grid/index.css.
42
+ const GRID_CSS = new URL("../grid/index.css", import.meta.url).href;
43
+ const GAPS = ["none", "xs", "sm", "md", "lg", "xl"];
44
+ let XmGrid = class XmGrid extends LitElement {
45
+ constructor() {
46
+ super(...arguments);
47
+ this.columns = 12;
48
+ this.gap = "md";
49
+ }
50
+ render() {
51
+ const gap = GAPS.includes(this.gap) ? this.gap : "md";
52
+ const columns = Number.isFinite(this.columns) && this.columns >= 1
53
+ ? Math.floor(this.columns)
54
+ : 12;
55
+ return html `
56
+ <link rel="stylesheet" href="${GRID_CSS}" />
57
+ <style>
58
+ :host {
59
+ display: block;
60
+ }
61
+ :host([hidden]) {
62
+ display: none;
63
+ }
64
+ </style>
65
+ <div
66
+ class="grid grid--gap-${gap}"
67
+ style="--xm-grid-columns: ${columns}"
68
+ >
69
+ <slot></slot>
70
+ </div>
71
+ `;
72
+ }
73
+ };
74
+ __decorate([
75
+ property({ type: Number, reflect: true })
76
+ ], XmGrid.prototype, "columns", void 0);
77
+ __decorate([
78
+ property({ type: String, reflect: true })
79
+ ], XmGrid.prototype, "gap", void 0);
80
+ XmGrid = __decorate([
81
+ customElement("xm-grid")
82
+ ], XmGrid);
@@ -0,0 +1,35 @@
1
+ /* ============================================
2
+ Kbd — inline keyboard-key token (key-cap).
3
+
4
+ <xm-kbd> presents a shortcut key (⌘, Enter, K) as a small
5
+ key-cap pill. Mono typescale (--xm-typescale-mono-*), a
6
+ surface-container-* backplate raised off the host surface, a
7
+ hairline 1px outline-variant border, and a subtle bottom edge
8
+ so it reads as a physical key. Ink is --md-sys-color-on-surface
9
+ to match the surface-container backplate (AD-13). Presentational
10
+ chrome — never a status hue (AD-11).
11
+ ============================================ */
12
+
13
+ .kbd {
14
+ display: inline-flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ box-sizing: border-box;
18
+ min-width: 20px;
19
+ padding: var(--s-0-5) var(--s-2);
20
+ border: 1px solid var(--md-sys-color-outline-variant);
21
+ /* Subtle bottom edge so the cap reads as a key, not a flat chip.
22
+ Hairline-respecting: the border stays 1px; this is an inset rim,
23
+ not a heavier border. */
24
+ border-bottom-width: 2px;
25
+ border-radius: var(--md-sys-shape-corner-extra-small);
26
+ background: var(--md-sys-color-surface-container-high);
27
+ color: var(--md-sys-color-on-surface);
28
+ font-family: var(--xm-typescale-mono-font);
29
+ font-size: var(--xm-typescale-mono-size);
30
+ font-weight: var(--xm-typescale-mono-weight);
31
+ line-height: var(--xm-typescale-mono-line-height);
32
+ letter-spacing: var(--xm-typescale-mono-tracking);
33
+ white-space: nowrap;
34
+ vertical-align: baseline;
35
+ }
@@ -0,0 +1,11 @@
1
+ import { LitElement } from "lit";
2
+ import type { TemplateResult } from "lit";
3
+ declare class XmKbd extends LitElement {
4
+ render(): TemplateResult;
5
+ }
6
+ declare global {
7
+ interface HTMLElementTagNameMap {
8
+ "xm-kbd": XmKbd;
9
+ }
10
+ }
11
+ export {};
@@ -0,0 +1,43 @@
1
+ /*
2
+ kbd/index.ts — <xm-kbd>.
3
+
4
+ Inline keyboard-key display primitive. Wraps its default-slot
5
+ content in a native <kbd> for semantics and renders it as a small
6
+ key-cap token using the JetBrains Mono typescale.
7
+
8
+ Authoring:
9
+ Press <xm-kbd>⌘</xm-kbd> <xm-kbd>K</xm-kbd> to open the palette.
10
+
11
+ Presentational, non-interactive — no focus ring. Shadow DOM for
12
+ <slot> projection; Lit is a bare `import` (peer dep); CSS via <link> resolved
13
+ relative to the built module (mirrors button/index.ts).
14
+ */
15
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
16
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
17
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
18
+ 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;
19
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
20
+ };
21
+ import { LitElement, html } from "lit";
22
+ import { customElement } from "lit/decorators.js";
23
+ const KBD_CSS = new URL("../kbd/index.css", import.meta.url).href;
24
+ let XmKbd = class XmKbd extends LitElement {
25
+ render() {
26
+ return html `
27
+ <link rel="stylesheet" href="${KBD_CSS}" />
28
+ <style>
29
+ :host {
30
+ display: inline-flex;
31
+ vertical-align: baseline;
32
+ }
33
+ :host([hidden]) {
34
+ display: none;
35
+ }
36
+ </style>
37
+ <kbd class="kbd"><slot></slot></kbd>
38
+ `;
39
+ }
40
+ };
41
+ XmKbd = __decorate([
42
+ customElement("xm-kbd")
43
+ ], XmKbd);
@@ -0,0 +1,15 @@
1
+ /* ============================================
2
+ List — vertical collection of <xm-list-item> rows.
3
+
4
+ Layout owner for a stack of rows. Sits on the surface* desk family
5
+ (AD-13) — establishes on-surface as the inherited ink so rows read
6
+ correctly on the desk. The rows own all chrome; the list owns the
7
+ rhythm, ARIA role, and (when interactive) keyboard selection.
8
+ ============================================ */
9
+
10
+ .list {
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: var(--s-1);
14
+ color: var(--md-sys-color-on-surface);
15
+ }
@@ -0,0 +1,28 @@
1
+ import { LitElement } from "lit";
2
+ import type { TemplateResult } from "lit";
3
+ type Primitive = string | number;
4
+ declare class XmList extends LitElement {
5
+ interactive: boolean;
6
+ selectable: boolean;
7
+ value: Primitive | null;
8
+ private get _isInteractive();
9
+ connectedCallback(): void;
10
+ disconnectedCallback(): void;
11
+ firstUpdated(): void;
12
+ private _items;
13
+ private _enabledItems;
14
+ private _applyItems;
15
+ private _updateRovingTabindex;
16
+ private _select;
17
+ private _itemFromEvent;
18
+ private _onClick;
19
+ private _onKeydown;
20
+ private _onSlot;
21
+ render(): TemplateResult;
22
+ }
23
+ declare global {
24
+ interface HTMLElementTagNameMap {
25
+ "xm-list": XmList;
26
+ }
27
+ }
28
+ export {};
@@ -0,0 +1,188 @@
1
+ /*
2
+ list/index.ts — <xm-list>.
3
+
4
+ Vertical collection of <xm-list-item> rows. Two modes:
5
+
6
+ - Presentational (default): role="list", items role="listitem". No
7
+ selection, no keyboard navigation — just a ruled stack.
8
+ - Interactive (`interactive` / `selectable`): the WAI-ARIA listbox
9
+ pattern — role="listbox", items role="option" + aria-selected, a
10
+ single tab stop (roving tabindex), ↑/↓ to move, Home/End to ends,
11
+ Enter/Space to select, disabled items skipped. Single-select.
12
+
13
+ Authoring:
14
+ <xm-list interactive aria-label="Workspaces">
15
+ <xm-list-item value="a" selected>Acme</xm-list-item>
16
+ <xm-list-item value="b">Globex</xm-list-item>
17
+ <xm-list-item value="c" disabled>Archived</xm-list-item>
18
+ </xm-list>
19
+
20
+ Properties:
21
+ interactive boolean — listbox selection + keyboard nav
22
+ selectable boolean — alias of interactive
23
+ value string|number — current selection (reflected to items)
24
+
25
+ Events:
26
+ change bubbles+composed. detail.value = the selected item's
27
+ primitive `value` (single), never the element (AD-8a).
28
+
29
+ Reads/sets only public item properties (value/selected/disabled) —
30
+ never their shadow roots (AD-12). Shadow DOM; Lit is a bare `import` (peer dep).
31
+ */
32
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
33
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
34
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
35
+ 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;
36
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
37
+ };
38
+ import { LitElement, html } from "lit";
39
+ import { customElement, property } from "lit/decorators.js";
40
+ const LIST_CSS = new URL("../list/index.css", import.meta.url).href;
41
+ let XmList = class XmList extends LitElement {
42
+ constructor() {
43
+ super(...arguments);
44
+ this.interactive = false;
45
+ this.selectable = false;
46
+ this.value = null;
47
+ this._onClick = (e) => {
48
+ if (!this._isInteractive)
49
+ return;
50
+ const item = this._itemFromEvent(e.composedPath()[0]);
51
+ if (item)
52
+ this._select(item);
53
+ };
54
+ this._onKeydown = (e) => {
55
+ if (!this._isInteractive)
56
+ return;
57
+ const k = e.key;
58
+ const items = this._enabledItems();
59
+ if (items.length === 0)
60
+ return;
61
+ const active = this._itemFromEvent(e.composedPath()[0]);
62
+ if (k === "Enter" || k === " ") {
63
+ if (active) {
64
+ e.preventDefault();
65
+ this._select(active);
66
+ }
67
+ return;
68
+ }
69
+ if (k !== "ArrowUp" && k !== "ArrowDown" && k !== "Home" && k !== "End")
70
+ return;
71
+ e.preventDefault();
72
+ const idx = active ? items.indexOf(active) : -1;
73
+ let next;
74
+ if (k === "Home")
75
+ next = 0;
76
+ else if (k === "End")
77
+ next = items.length - 1;
78
+ else if (k === "ArrowDown")
79
+ next = idx < 0 ? 0 : (idx + 1) % items.length;
80
+ else
81
+ next = idx < 0 ? items.length - 1 : (idx - 1 + items.length) % items.length;
82
+ const target = items[next];
83
+ if (target) {
84
+ this._updateRovingTabindex(target);
85
+ target.focus();
86
+ }
87
+ };
88
+ this._onSlot = () => {
89
+ this._applyItems();
90
+ };
91
+ }
92
+ get _isInteractive() {
93
+ return this.interactive || this.selectable;
94
+ }
95
+ connectedCallback() {
96
+ super.connectedCallback();
97
+ this.addEventListener("click", this._onClick);
98
+ this.addEventListener("keydown", this._onKeydown);
99
+ }
100
+ disconnectedCallback() {
101
+ super.disconnectedCallback();
102
+ this.removeEventListener("click", this._onClick);
103
+ this.removeEventListener("keydown", this._onKeydown);
104
+ }
105
+ firstUpdated() {
106
+ this._applyItems();
107
+ }
108
+ _items() {
109
+ return [...this.children].filter((c) => c.tagName.toLowerCase() === "xm-list-item");
110
+ }
111
+ _enabledItems() {
112
+ return this._items().filter((c) => !c.disabled);
113
+ }
114
+ _applyItems() {
115
+ const items = this._items();
116
+ const interactive = this._isInteractive;
117
+ const selected = items.find((c) => c.selected) ?? null;
118
+ if (interactive)
119
+ this.value = selected?.value ?? null;
120
+ items.forEach((el) => {
121
+ el.setAttribute("role", interactive ? "option" : "listitem");
122
+ if (interactive) {
123
+ el.setAttribute("aria-selected", el.selected ? "true" : "false");
124
+ if (el.disabled)
125
+ el.setAttribute("aria-disabled", "true");
126
+ else
127
+ el.removeAttribute("aria-disabled");
128
+ }
129
+ else {
130
+ el.removeAttribute("aria-selected");
131
+ }
132
+ });
133
+ if (interactive)
134
+ this._updateRovingTabindex(selected ?? this._enabledItems()[0] ?? undefined);
135
+ }
136
+ _updateRovingTabindex(focused) {
137
+ const items = this._items();
138
+ const enabled = this._enabledItems();
139
+ const tabStop = focused ?? enabled.find((c) => c.selected) ?? enabled[0] ?? null;
140
+ items.forEach((c) => {
141
+ c.tabIndex = c === tabStop && !c.disabled ? 0 : -1;
142
+ });
143
+ }
144
+ _select(item) {
145
+ if (item.disabled)
146
+ return;
147
+ this._items().forEach((c) => (c.selected = c === item));
148
+ this._applyItems();
149
+ this.dispatchEvent(new CustomEvent("change", {
150
+ detail: { value: item.value },
151
+ bubbles: true,
152
+ composed: true,
153
+ }));
154
+ }
155
+ _itemFromEvent(target) {
156
+ const el = target;
157
+ const item = el?.closest("xm-list-item");
158
+ return item && item.parentElement === this ? item : null;
159
+ }
160
+ render() {
161
+ return html `
162
+ <link rel="stylesheet" href="${LIST_CSS}" />
163
+ <style>
164
+ :host {
165
+ display: block;
166
+ }
167
+ :host([hidden]) {
168
+ display: none;
169
+ }
170
+ </style>
171
+ <div class="list" role=${this._isInteractive ? "listbox" : "list"}>
172
+ <slot @slotchange=${this._onSlot}></slot>
173
+ </div>
174
+ `;
175
+ }
176
+ };
177
+ __decorate([
178
+ property({ type: Boolean })
179
+ ], XmList.prototype, "interactive", void 0);
180
+ __decorate([
181
+ property({ type: Boolean })
182
+ ], XmList.prototype, "selectable", void 0);
183
+ __decorate([
184
+ property({ attribute: false })
185
+ ], XmList.prototype, "value", void 0);
186
+ XmList = __decorate([
187
+ customElement("xm-list")
188
+ ], XmList);
@@ -0,0 +1,119 @@
1
+ /* ============================================
2
+ List item — a row in <xm-list>.
3
+
4
+ Generalizes the shipped <xm-sidebar-item> desk-row pattern (FR-156):
5
+ 8px radius, surface-container-lowest hover/active, short3 background
6
+ transition, on-surface / on-surface-variant ink — into a reusable
7
+ surface* row with leading / body (title + optional secondary) /
8
+ trailing slots.
9
+
10
+ Lives on the surface* desk family (AD-13): primary ink
11
+ --md-sys-color-on-surface, secondary --md-sys-color-on-surface-variant.
12
+ Selected adds a coral leading indicator + a soft selected background;
13
+ selection is conveyed by the indicator + aria-selected, never by color
14
+ alone (NFR-15). State changes are real token swaps, not filter/opacity.
15
+ ============================================ */
16
+
17
+ .list-item {
18
+ display: flex;
19
+ align-items: center;
20
+ gap: var(--s-3);
21
+ padding: var(--s-2) var(--s-3);
22
+ border-radius: var(--md-sys-shape-corner-small);
23
+ color: var(--md-sys-color-on-surface);
24
+ cursor: pointer;
25
+ position: relative;
26
+ transition:
27
+ background var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
28
+ color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard);
29
+ }
30
+ .list-item:focus { outline: none; }
31
+
32
+ /* ---------- Hover / active (shared with sidebar-item) ---------- */
33
+ .list-item:hover,
34
+ .list-item.is-hover {
35
+ background: var(--md-sys-color-surface-container-lowest);
36
+ }
37
+
38
+ /* ---------- Selected — coral leading indicator + soft fill ---------- */
39
+ .list-item--selected {
40
+ background: var(--md-sys-color-surface-container-low);
41
+ }
42
+ .list-item--selected::before {
43
+ content: "";
44
+ position: absolute;
45
+ left: 0;
46
+ top: 50%;
47
+ transform: translateY(-50%);
48
+ width: 3px;
49
+ height: 60%;
50
+ border-radius: var(--md-sys-shape-corner-full);
51
+ background: var(--md-sys-color-primary);
52
+ }
53
+ .list-item--selected .list-item__title {
54
+ color: var(--md-sys-color-on-surface);
55
+ }
56
+
57
+ /* ---------- Disabled — reduced emphasis, no hover ---------- */
58
+ .list-item--disabled {
59
+ cursor: not-allowed;
60
+ color: var(--xm-color-on-surface-disabled);
61
+ }
62
+ .list-item--disabled:hover,
63
+ .list-item--disabled.is-hover {
64
+ background: transparent;
65
+ }
66
+
67
+ /* ---------- Leading / body / trailing ---------- */
68
+ .list-item__leading {
69
+ display: inline-flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ flex-shrink: 0;
73
+ color: var(--md-sys-color-on-surface-variant);
74
+ }
75
+ .list-item__leading.is-empty { display: none; }
76
+
77
+ .list-item__body {
78
+ flex: 1;
79
+ min-width: 0;
80
+ display: flex;
81
+ flex-direction: column;
82
+ gap: 1px;
83
+ }
84
+
85
+ .list-item__title {
86
+ font-family: var(--md-sys-typescale-title-small-font);
87
+ font-size: var(--md-sys-typescale-title-small-size);
88
+ font-weight: 500;
89
+ line-height: 1.25;
90
+ color: var(--md-sys-color-on-surface);
91
+ overflow: hidden;
92
+ text-overflow: ellipsis;
93
+ white-space: nowrap;
94
+ }
95
+
96
+ .list-item__secondary {
97
+ font-family: var(--md-sys-typescale-body-medium-font);
98
+ font-size: var(--md-sys-typescale-body-medium-size);
99
+ line-height: 1.35;
100
+ color: var(--md-sys-color-on-surface-variant);
101
+ overflow: hidden;
102
+ text-overflow: ellipsis;
103
+ white-space: nowrap;
104
+ }
105
+ .list-item__secondary.is-empty { display: none; }
106
+
107
+ .list-item__trailing {
108
+ display: inline-flex;
109
+ align-items: center;
110
+ flex-shrink: 0;
111
+ color: var(--md-sys-color-on-surface-variant);
112
+ }
113
+ .list-item__trailing.is-empty { display: none; }
114
+
115
+ /* Dark theme — like sidebar-item, push the resting title into gray so a
116
+ hovered/selected row's brighter title pops as the active state. */
117
+ [data-theme="dark"] .list-item:not(.list-item--selected):not(:hover):not(.is-hover) .list-item__title {
118
+ color: var(--md-sys-color-on-surface-variant);
119
+ }
@@ -0,0 +1,20 @@
1
+ import { LitElement } from "lit";
2
+ import type { TemplateResult } from "lit";
3
+ declare class XmListItem extends LitElement {
4
+ value: string;
5
+ selected: boolean;
6
+ disabled: boolean;
7
+ secondary: string;
8
+ private _mo;
9
+ private _hasSlot;
10
+ connectedCallback(): void;
11
+ disconnectedCallback(): void;
12
+ private _onSlot;
13
+ render(): TemplateResult;
14
+ }
15
+ declare global {
16
+ interface HTMLElementTagNameMap {
17
+ "xm-list-item": XmListItem;
18
+ }
19
+ }
20
+ export {};
@@ -0,0 +1,127 @@
1
+ /*
2
+ list-item/index.ts — <xm-list-item>.
3
+
4
+ A row in <xm-list>. Generalizes the shipped <xm-sidebar-item> desk-row
5
+ pattern into a reusable shadow-DOM row with leading / body / trailing
6
+ regions. The body holds a primary title (default slot) and an optional
7
+ secondary line (`secondary` attribute or slot="secondary").
8
+
9
+ Authoring:
10
+ <xm-list-item value="threads" secondary="14 open">
11
+ <svg slot="leading">…</svg>
12
+ Threads
13
+ <span slot="trailing">⌘1</span>
14
+ </xm-list-item>
15
+
16
+ Properties:
17
+ value string|number — the item's identity (list emits this)
18
+ selected boolean — selected row (coral leading indicator)
19
+ disabled boolean — non-interactive, reduced emphasis
20
+ secondary string — secondary line text (or use slot="secondary")
21
+
22
+ Focus model: the host element is the focus target so a parent <xm-list>
23
+ drives roving tabindex without crossing the shadow boundary (AD-12). The
24
+ list sets role (listitem | option) + aria-selected on the host; Enter/Space
25
+ and click activate. Shadow DOM; Lit is a bare `import` (peer dep).
26
+ */
27
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
28
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
29
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
30
+ 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;
31
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
32
+ };
33
+ import { LitElement, html } from "lit";
34
+ import { customElement, property } from "lit/decorators.js";
35
+ const ITEM_CSS = new URL("../list-item/index.css", import.meta.url).href;
36
+ let XmListItem = class XmListItem extends LitElement {
37
+ constructor() {
38
+ super(...arguments);
39
+ this.value = "";
40
+ this.selected = false;
41
+ this.disabled = false;
42
+ this.secondary = "";
43
+ this._mo = null;
44
+ this._onSlot = () => {
45
+ this.requestUpdate();
46
+ };
47
+ }
48
+ _hasSlot(name) {
49
+ for (const node of this.childNodes) {
50
+ if (node.nodeType === Node.ELEMENT_NODE &&
51
+ node.getAttribute("slot") === name) {
52
+ return true;
53
+ }
54
+ }
55
+ return false;
56
+ }
57
+ connectedCallback() {
58
+ super.connectedCallback();
59
+ if (!this._mo) {
60
+ this._mo = new MutationObserver(() => this.requestUpdate());
61
+ this._mo.observe(this, { childList: true, characterData: true, subtree: true });
62
+ }
63
+ }
64
+ disconnectedCallback() {
65
+ super.disconnectedCallback();
66
+ this._mo?.disconnect();
67
+ this._mo = null;
68
+ }
69
+ render() {
70
+ const cls = [
71
+ "list-item",
72
+ this.selected && "list-item--selected",
73
+ this.disabled && "list-item--disabled",
74
+ ]
75
+ .filter(Boolean)
76
+ .join(" ");
77
+ const hasLeading = this._hasSlot("leading");
78
+ const hasTrailing = this._hasSlot("trailing");
79
+ const hasSecondary = this.secondary || this._hasSlot("secondary");
80
+ return html `
81
+ <link rel="stylesheet" href="${ITEM_CSS}" />
82
+ <style>
83
+ :host {
84
+ display: block;
85
+ outline: none;
86
+ }
87
+ :host([hidden]) {
88
+ display: none;
89
+ }
90
+ :host(:focus-visible) .list-item {
91
+ box-shadow: var(--xm-state-focus-ring);
92
+ }
93
+ </style>
94
+ <div class="${cls}">
95
+ <span class="list-item__leading ${hasLeading ? "" : "is-empty"}"
96
+ ><slot name="leading" @slotchange=${this._onSlot}></slot
97
+ ></span>
98
+ <span class="list-item__body">
99
+ <span class="list-item__title"><slot></slot></span>
100
+ <span class="list-item__secondary ${hasSecondary ? "" : "is-empty"}">
101
+ ${this.secondary
102
+ ? this.secondary
103
+ : html `<slot name="secondary" @slotchange=${this._onSlot}></slot>`}
104
+ </span>
105
+ </span>
106
+ <span class="list-item__trailing ${hasTrailing ? "" : "is-empty"}"
107
+ ><slot name="trailing" @slotchange=${this._onSlot}></slot
108
+ ></span>
109
+ </div>
110
+ `;
111
+ }
112
+ };
113
+ __decorate([
114
+ property({ type: String })
115
+ ], XmListItem.prototype, "value", void 0);
116
+ __decorate([
117
+ property({ type: Boolean, reflect: true })
118
+ ], XmListItem.prototype, "selected", void 0);
119
+ __decorate([
120
+ property({ type: Boolean, reflect: true })
121
+ ], XmListItem.prototype, "disabled", void 0);
122
+ __decorate([
123
+ property({ type: String })
124
+ ], XmListItem.prototype, "secondary", void 0);
125
+ XmListItem = __decorate([
126
+ customElement("xm-list-item")
127
+ ], XmListItem);