@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,126 @@
1
+ /* ============================================
2
+ xm-checkbox — toggle field built on XmField.
3
+
4
+ A compact, inline toggle: the box sits BESIDE the label (toggle layout),
5
+ not above it like the bordered text-field chrome. State machinery
6
+ (disabled / focus / form-association / change event) is inherited from
7
+ XmField; this file only styles the box, the mark, and the label row.
8
+
9
+ Surface & ink: the checkbox rides the card tier shared by every XmField
10
+ sibling — ink is --md-sys-color-inverse-on-surface, the box hairline is
11
+ --md-sys-color-outline-variant. Selected (checked OR indeterminate) fills
12
+ the box with the single coral accent (--md-sys-color-primary) and paints
13
+ the mark in --md-sys-color-on-primary. Coral signals selection only —
14
+ never severity (AD-11 / rule 3a). The error string in the message row
15
+ carries severity via copy, not color.
16
+
17
+ BEM block: `checkbox`. Registered in scripts/check-bem.sh STRICT_BLOCKS.
18
+ ============================================ */
19
+
20
+ .checkbox {
21
+ display: inline-flex;
22
+ flex-direction: column;
23
+ gap: var(--s-1);
24
+ }
25
+
26
+ /* ---------- Control row — box + label, the focusable target ---------- */
27
+ .checkbox__control {
28
+ display: inline-flex;
29
+ align-items: center;
30
+ gap: var(--s-2);
31
+ cursor: pointer;
32
+ user-select: none;
33
+ border-radius: var(--md-sys-shape-corner-extra-small);
34
+ outline: none;
35
+ }
36
+
37
+ /* ---------- Box — the 18px square that holds the mark ---------- */
38
+ .checkbox__box {
39
+ position: relative;
40
+ display: inline-flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ width: 18px;
44
+ height: 18px;
45
+ flex-shrink: 0;
46
+ box-sizing: border-box;
47
+ border: 1px solid var(--md-sys-color-outline-variant);
48
+ border-radius: var(--md-sys-shape-corner-extra-small);
49
+ background: transparent;
50
+ color: var(--md-sys-color-on-primary);
51
+ transition:
52
+ background var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
53
+ border-color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
54
+ box-shadow var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard);
55
+ }
56
+
57
+ .checkbox__control:hover .checkbox__box {
58
+ border-color: var(--md-sys-color-outline);
59
+ }
60
+
61
+ /* Focus ring — the canonical 3px coral halo, never a hard-coded width. */
62
+ .checkbox__control:focus-visible .checkbox__box {
63
+ border-color: var(--md-sys-color-primary);
64
+ box-shadow: var(--xm-state-focus-ring);
65
+ }
66
+
67
+ /* ---------- Mark — primitives check / indeterminate icon ---------- */
68
+ .checkbox__mark {
69
+ display: inline-flex;
70
+ color: var(--md-sys-color-on-primary);
71
+ }
72
+
73
+ /* ---------- Label ---------- */
74
+ .checkbox__label {
75
+ color: var(--md-sys-color-inverse-on-surface);
76
+ font:
77
+ var(--md-sys-typescale-body-medium-weight)
78
+ var(--md-sys-typescale-body-medium-size) /
79
+ var(--md-sys-typescale-body-medium-line-height)
80
+ var(--md-sys-typescale-body-medium-font);
81
+ letter-spacing: 0;
82
+ }
83
+
84
+ /* ---------- Selected — checked OR indeterminate ---------- */
85
+ .checkbox--selected .checkbox__box {
86
+ background: var(--md-sys-color-primary);
87
+ border-color: var(--md-sys-color-primary);
88
+ }
89
+ .checkbox--selected .checkbox__control:hover .checkbox__box {
90
+ border-color: var(--md-sys-color-primary);
91
+ }
92
+
93
+ /* ---------- Disabled — shared reduced emphasis; hover reverted ---------- */
94
+ .checkbox--disabled .checkbox__control {
95
+ cursor: not-allowed;
96
+ }
97
+ .checkbox--disabled .checkbox__box {
98
+ opacity: 0.45;
99
+ box-shadow: none;
100
+ }
101
+ .checkbox--disabled .checkbox__label {
102
+ color: var(--xm-color-inverse-on-surface-disabled);
103
+ }
104
+ .checkbox--disabled .checkbox__control:hover .checkbox__box {
105
+ border-color: var(--md-sys-color-outline-variant);
106
+ }
107
+ .checkbox--disabled.checkbox--selected .checkbox__control:hover .checkbox__box {
108
+ border-color: var(--md-sys-color-primary);
109
+ }
110
+
111
+ /* ---------- Helper / error message row ----------
112
+ Severity is copy, not color — the error keeps the helper ink (rule 3a). */
113
+ .checkbox__message {
114
+ padding-left: calc(var(--s-4-5) + var(--s-2));
115
+ font:
116
+ var(--md-sys-typescale-body-small-weight)
117
+ var(--md-sys-typescale-body-small-size) /
118
+ var(--md-sys-typescale-body-small-line-height)
119
+ var(--md-sys-typescale-body-small-font);
120
+ }
121
+ .checkbox__message--helper {
122
+ color: var(--xm-color-inverse-on-surface-muted);
123
+ }
124
+ .checkbox__message--error {
125
+ color: var(--md-sys-color-inverse-on-surface);
126
+ }
@@ -0,0 +1,21 @@
1
+ import type { TemplateResult } from "lit";
2
+ import { XmField } from "../field/index.js";
3
+ declare class XmCheckbox extends XmField {
4
+ /** Indeterminate (mixed) state — shows the dash mark on the coral fill and
5
+ reports aria-checked="mixed". Cleared on the first user toggle. */
6
+ indeterminate: boolean;
7
+ /** A checkbox submits checked-state ("on"/null), not text — declare it a
8
+ toggle so the form value is correct from first paint, before any user
9
+ interaction (the base seeds `_checked` from the `checked` attribute and
10
+ `isToggle` routes it through the checkbox form-value branch). */
11
+ protected get isToggle(): boolean;
12
+ private _handleToggle;
13
+ private _onKeydown;
14
+ render(): TemplateResult;
15
+ }
16
+ declare global {
17
+ interface HTMLElementTagNameMap {
18
+ "xm-checkbox": XmCheckbox;
19
+ }
20
+ }
21
+ export {};
@@ -0,0 +1,138 @@
1
+ /*
2
+ checkbox/index.ts — <xm-checkbox>, a toggle field built on XmField.
3
+
4
+ Story 2.5. A labeled, tri-state checkbox: checked · indeterminate ·
5
+ disabled, with a default-slot label. Selected (checked or indeterminate)
6
+ paints the box coral (--md-sys-color-primary) and the mark in
7
+ --md-sys-color-on-primary. Severity is never relevant here — coral is
8
+ used only for the selected state (AD-11 / UX-DR3).
9
+
10
+ Why this overrides render() instead of renderControl(): a checkbox is a
11
+ TOGGLE field — the label sits BESIDE the control, not above it on its own
12
+ row. XmField.render() lays out label-row / bordered-control-wrapper /
13
+ helper-row, which is the text-field chrome and wrong for a compact toggle.
14
+ So the subclass renders the inline box+mark+label row itself, but reuses
15
+ ALL of the base's state machinery: effectiveDisabled / nonInteractive,
16
+ the emitToggle helper (which owns live _checked state, the form value, and
17
+ the bubbling+composed `change` with detail.checked), the controlAria hooks,
18
+ and the helper/error message row. Nothing about disabled/focus/form-
19
+ association is re-implemented.
20
+
21
+ Authoring:
22
+ <xm-checkbox checked>Email me about replies</xm-checkbox>
23
+ <xm-checkbox indeterminate>Select all</xm-checkbox>
24
+ <xm-checkbox disabled>Locked option</xm-checkbox>
25
+
26
+ Shadow DOM (Lit <slot> projection needs it). Lit is a bare `import` (peer dep). The sibling index.css + primitives CSS are <link>ed inside
27
+ the shadow root, resolved from the *built* file location.
28
+ */
29
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
30
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
32
+ 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;
33
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
34
+ };
35
+ import { html, nothing } from "lit";
36
+ import { customElement, property } from "lit/decorators.js";
37
+ import { XmField } from "../field/index.js";
38
+ const CHECKBOX_CSS = new URL("../checkbox/index.css", import.meta.url).href;
39
+ const PRIMITIVES_CSS = new URL("../primitives/index.css", import.meta.url).href;
40
+ let XmCheckbox = class XmCheckbox extends XmField {
41
+ constructor() {
42
+ super(...arguments);
43
+ /** Indeterminate (mixed) state — shows the dash mark on the coral fill and
44
+ reports aria-checked="mixed". Cleared on the first user toggle. */
45
+ this.indeterminate = false;
46
+ this._handleToggle = () => {
47
+ if (this.nonInteractive)
48
+ return;
49
+ const next = this.indeterminate ? true : !this._checked;
50
+ this.indeterminate = false;
51
+ this.emitToggle(next);
52
+ };
53
+ this._onKeydown = (event) => {
54
+ if (event.key === " " || event.key === "Spacebar") {
55
+ event.preventDefault();
56
+ this._handleToggle();
57
+ }
58
+ };
59
+ }
60
+ /** A checkbox submits checked-state ("on"/null), not text — declare it a
61
+ toggle so the form value is correct from first paint, before any user
62
+ interaction (the base seeds `_checked` from the `checked` attribute and
63
+ `isToggle` routes it through the checkbox form-value branch). */
64
+ get isToggle() {
65
+ return true;
66
+ }
67
+ render() {
68
+ const selected = this._checked || this.indeterminate;
69
+ const cls = [
70
+ "checkbox",
71
+ selected ? "checkbox--selected" : "",
72
+ this.effectiveDisabled ? "checkbox--disabled" : "",
73
+ this.isError ? "checkbox--error" : "",
74
+ ]
75
+ .filter(Boolean)
76
+ .join(" ");
77
+ const ariaChecked = this.indeterminate ? "mixed" : String(this._checked);
78
+ const helperText = this.isError ? this.error : this.helper;
79
+ const ctrl = this.controlAria;
80
+ return html `
81
+ <link rel="stylesheet" href="${PRIMITIVES_CSS}" />
82
+ <link rel="stylesheet" href="${CHECKBOX_CSS}" />
83
+ <style>
84
+ :host {
85
+ display: inline-block;
86
+ }
87
+ :host([hidden]) {
88
+ display: none;
89
+ }
90
+ </style>
91
+ <div class="${cls}">
92
+ <div
93
+ class="checkbox__control"
94
+ id="${ctrl.id}"
95
+ role="checkbox"
96
+ aria-checked="${ariaChecked}"
97
+ aria-describedby="${helperText ? ctrl.describedBy : nothing}"
98
+ aria-invalid="${ctrl.invalid ?? nothing}"
99
+ aria-required="${ctrl.required ?? nothing}"
100
+ aria-disabled="${this.effectiveDisabled ? "true" : nothing}"
101
+ tabindex="${this.effectiveDisabled ? -1 : 0}"
102
+ @click=${this._handleToggle}
103
+ @keydown=${this._onKeydown}
104
+ >
105
+ <span class="checkbox__box" aria-hidden="true">
106
+ ${this.indeterminate
107
+ ? html `<xm-indeterminate-icon
108
+ class="checkbox__mark"
109
+ size="14"
110
+ ></xm-indeterminate-icon>`
111
+ : this._checked
112
+ ? html `<xm-check-icon class="checkbox__mark" size="14"></xm-check-icon>`
113
+ : nothing}
114
+ </span>
115
+ <span class="checkbox__label"><slot></slot></span>
116
+ </div>
117
+
118
+ ${helperText
119
+ ? html `<div
120
+ class="checkbox__message ${this.isError
121
+ ? "checkbox__message--error"
122
+ : "checkbox__message--helper"}"
123
+ id="${ctrl.describedBy}"
124
+ role=${this.isError ? "alert" : nothing}
125
+ >
126
+ ${helperText}
127
+ </div>`
128
+ : nothing}
129
+ </div>
130
+ `;
131
+ }
132
+ };
133
+ __decorate([
134
+ property({ type: Boolean, reflect: true })
135
+ ], XmCheckbox.prototype, "indeterminate", void 0);
136
+ XmCheckbox = __decorate([
137
+ customElement("xm-checkbox")
138
+ ], XmCheckbox);
@@ -0,0 +1,145 @@
1
+ /* ============================================
2
+ Chip — compact label pill (tag / filter / category).
3
+
4
+ <xm-chip> is a transparent pill that sits on a surface* host, so
5
+ default ink is --md-sys-color-on-surface (AD-13). Selected flips
6
+ to the coral primary-container fill + on-primary-container ink —
7
+ the selected-pill pattern (UX-DR3), never a status hue (AD-11).
8
+ Removable chips carry a trailing x. A category chip may take a
9
+ saturated background ONLY via the --xm-method-* / --xm-ext-* sets
10
+ (category, never severity); --md-sys-color-error* is forbidden.
11
+
12
+ Sizes share the canonical xs|sm|md|lg axis (AD-9).
13
+ ============================================ */
14
+
15
+ .chip {
16
+ --chip-height: 28px;
17
+ --chip-pad-x: var(--s-3);
18
+ --chip-font: var(--md-sys-typescale-label-medium-size);
19
+ --chip-gap: var(--s-1);
20
+
21
+ display: inline-flex;
22
+ align-items: center;
23
+ gap: var(--chip-gap);
24
+ box-sizing: border-box;
25
+ height: var(--chip-height);
26
+ padding: 0 var(--chip-pad-x);
27
+ border: 1px solid var(--md-sys-color-outline-variant);
28
+ border-radius: var(--md-sys-shape-corner-full);
29
+ background: transparent;
30
+ color: var(--md-sys-color-on-surface);
31
+ font-family: var(--md-sys-typescale-label-medium-font);
32
+ font-size: var(--chip-font);
33
+ font-weight: 500;
34
+ line-height: 1;
35
+ letter-spacing: 0;
36
+ white-space: nowrap;
37
+ cursor: pointer;
38
+ user-select: none;
39
+ transition:
40
+ background var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
41
+ color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
42
+ border-color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard);
43
+ }
44
+ /* The host element owns focus (role=button + tabindex on <xm-chip>) so the
45
+ 3px ring is applied via :host(:focus-visible) .chip in the component's
46
+ inline <style>. The forced-state helper still previews it. */
47
+ .chip.is-focus {
48
+ box-shadow: var(--xm-state-focus-ring);
49
+ }
50
+ .chip:hover,
51
+ .chip.is-hover {
52
+ background: color-mix(in oklab, var(--md-sys-color-on-surface) var(--md-sys-state-hover-state-layer-opacity), transparent);
53
+ border-color: var(--md-sys-color-outline);
54
+ }
55
+
56
+ /* ---------- Selected — coral selected-pill ---------- */
57
+ .chip--selected {
58
+ border-color: color-mix(in oklab, var(--md-sys-color-primary) 60%, transparent);
59
+ background: var(--md-sys-color-primary-container);
60
+ color: var(--md-sys-color-on-primary-container);
61
+ }
62
+ .chip--selected:hover,
63
+ .chip--selected.is-hover {
64
+ background: var(--md-sys-color-primary-container);
65
+ border-color: var(--md-sys-color-primary);
66
+ }
67
+
68
+ /* ---------- Category — saturated ONLY for category (AD-11) ---------- */
69
+ .chip--category-get { background: var(--xm-method-get-bg); color: var(--xm-method-get-ink); border-color: transparent; }
70
+ .chip--category-post { background: var(--xm-method-post-bg); color: var(--xm-method-post-ink); border-color: transparent; }
71
+ .chip--category-put { background: var(--xm-method-put-bg); color: var(--xm-method-put-ink); border-color: transparent; }
72
+ .chip--category-delete { background: var(--xm-method-delete-bg); color: var(--xm-method-delete-ink); border-color: transparent; }
73
+ .chip--category-patch { background: var(--xm-method-patch-bg); color: var(--xm-method-patch-ink); border-color: transparent; }
74
+ .chip--category-yml { background: var(--xm-ext-yml-bg); color: var(--xm-ext-yml-ink); border-color: transparent; }
75
+ .chip--category-json { background: var(--xm-ext-json-bg); color: var(--xm-ext-json-ink); border-color: transparent; }
76
+ /* Category chips don't change hue on hover — the hue IS the meaning. */
77
+ .chip--category-get:hover, .chip--category-get.is-hover,
78
+ .chip--category-post:hover, .chip--category-post.is-hover,
79
+ .chip--category-put:hover, .chip--category-put.is-hover,
80
+ .chip--category-delete:hover, .chip--category-delete.is-hover,
81
+ .chip--category-patch:hover, .chip--category-patch.is-hover,
82
+ .chip--category-yml:hover, .chip--category-yml.is-hover,
83
+ .chip--category-json:hover, .chip--category-json.is-hover {
84
+ filter: none;
85
+ border-color: transparent;
86
+ }
87
+
88
+ /* ---------- Disabled — shared reduced-emphasis ---------- */
89
+ .chip--disabled {
90
+ cursor: not-allowed;
91
+ color: var(--xm-color-on-surface-disabled);
92
+ border-color: var(--md-sys-color-outline-variant);
93
+ }
94
+ .chip--disabled:hover,
95
+ .chip--disabled.is-hover {
96
+ background: transparent;
97
+ border-color: var(--md-sys-color-outline-variant);
98
+ }
99
+ .chip--selected.chip--disabled:hover {
100
+ background: var(--md-sys-color-primary-container);
101
+ }
102
+
103
+ /* ---------- Sizes ---------- */
104
+ .chip--xs { --chip-height: 22px; --chip-pad-x: var(--s-2); --chip-font: var(--md-sys-typescale-label-small-size); --chip-gap: 3px; }
105
+ .chip--sm { --chip-height: 24px; --chip-pad-x: var(--s-2); --chip-font: var(--md-sys-typescale-label-small-size); }
106
+ .chip--md { --chip-height: 28px; --chip-pad-x: var(--s-3); --chip-font: var(--md-sys-typescale-label-medium-size); }
107
+ .chip--lg { --chip-height: 34px; --chip-pad-x: var(--s-4); --chip-font: var(--md-sys-typescale-label-large-size); }
108
+
109
+ /* ---------- Inner elements ---------- */
110
+ .chip__icon {
111
+ display: inline-flex;
112
+ align-items: center;
113
+ flex-shrink: 0;
114
+ }
115
+ .chip__icon.is-empty { display: none; }
116
+
117
+ .chip__label {
118
+ display: inline-block;
119
+ }
120
+
121
+ .chip__remove {
122
+ appearance: none;
123
+ display: inline-flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ flex-shrink: 0;
127
+ width: 16px;
128
+ height: 16px;
129
+ margin-left: var(--s-0-5);
130
+ margin-right: -2px;
131
+ padding: 0;
132
+ border: none;
133
+ border-radius: var(--md-sys-shape-corner-full);
134
+ background: transparent;
135
+ color: currentColor;
136
+ cursor: pointer;
137
+ transition: background var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard);
138
+ }
139
+ .chip__remove:hover {
140
+ background: color-mix(in oklab, currentColor 18%, transparent);
141
+ }
142
+ .chip__remove:focus-visible {
143
+ outline: none;
144
+ box-shadow: var(--xm-state-focus-ring);
145
+ }
@@ -0,0 +1,30 @@
1
+ import { LitElement } from "lit";
2
+ import type { TemplateResult } from "lit";
3
+ type ChipSize = "xs" | "sm" | "md" | "lg";
4
+ type ChipCategory = "get" | "post" | "put" | "delete" | "patch" | "yml" | "json" | "";
5
+ declare class XmChip extends LitElement {
6
+ value: string;
7
+ size: ChipSize;
8
+ selected: boolean;
9
+ removable: boolean;
10
+ disabled: boolean;
11
+ category: ChipCategory;
12
+ private _mo;
13
+ private get _labelText();
14
+ private get _hasIconLeft();
15
+ connectedCallback(): void;
16
+ disconnectedCallback(): void;
17
+ updated(): void;
18
+ private _onSlot;
19
+ private _activate;
20
+ private _onHostClick;
21
+ private _onHostKeydown;
22
+ private _onRemove;
23
+ render(): TemplateResult;
24
+ }
25
+ declare global {
26
+ interface HTMLElementTagNameMap {
27
+ "xm-chip": XmChip;
28
+ }
29
+ }
30
+ export {};
@@ -0,0 +1,230 @@
1
+ /*
2
+ chip/index.ts — <xm-chip>.
3
+
4
+ Compact label pill (tag / filter / category). Default slot is the
5
+ label; an optional icon-left slot leads it; a trailing remove ×
6
+ appears when `removable`.
7
+
8
+ Authoring:
9
+ <xm-chip value="rest">REST</xm-chip>
10
+ <xm-chip value="planner" selected>Planning Path</xm-chip>
11
+ <xm-chip value="design.yaml" removable>design.yaml</xm-chip>
12
+ <xm-chip value="get" category="get">GET</xm-chip>
13
+
14
+ Properties:
15
+ value string|number — the chip's identity (group emits this)
16
+ size xs|sm|md|lg (default md)
17
+ selected boolean — coral selected-pill (UX-DR3)
18
+ removable boolean — show trailing × → emits xm-chip-remove
19
+ disabled boolean — non-interactive, reduced emphasis
20
+ category get|post|put|delete|patch|yml|json — saturated category
21
+ background via --xm-method-* / --xm-ext-* (category,
22
+ NEVER severity; AD-11)
23
+
24
+ Events:
25
+ xm-chip-remove bubbles+composed; detail { value }
26
+ xm-chip-activate bubbles+composed+cancelable; detail { value };
27
+ a parent <xm-chip-group> calls preventDefault to
28
+ own selection. Standalone, the chip self-toggles.
29
+
30
+ Focus model: the host element itself is the focus target (role=button,
31
+ aria-pressed, tabindex on the host) so a parent group can drive roving
32
+ focus by setting `chip.tabIndex` without crossing the shadow boundary
33
+ (AD-12). Enter/Space activate; the remove control is a real focusable
34
+ <button> in the shadow root. Selection is conveyed via aria-pressed,
35
+ not color alone (NFR-15). Shadow DOM; Lit is a bare `import` (peer dep).
36
+ */
37
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
38
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
39
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
40
+ 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;
41
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
42
+ };
43
+ import { LitElement, html, nothing } from "lit";
44
+ import { customElement, property } from "lit/decorators.js";
45
+ const CHIP_CSS = new URL("../chip/index.css", import.meta.url).href;
46
+ let XmChip = class XmChip extends LitElement {
47
+ constructor() {
48
+ super(...arguments);
49
+ this.value = "";
50
+ this.size = "md";
51
+ this.selected = false;
52
+ this.removable = false;
53
+ this.disabled = false;
54
+ this.category = "";
55
+ this._mo = null;
56
+ this._onSlot = () => {
57
+ this.requestUpdate();
58
+ };
59
+ this._onHostClick = (e) => {
60
+ if (this.disabled)
61
+ return;
62
+ // Clicks on the remove control are handled separately.
63
+ const path = e.composedPath();
64
+ if (path.some((n) => n.classList?.contains("chip__remove"))) {
65
+ return;
66
+ }
67
+ this._activate();
68
+ };
69
+ this._onHostKeydown = (e) => {
70
+ if (this.disabled)
71
+ return;
72
+ // Enter/Space on the remove control removes — it must NOT also toggle
73
+ // selection (the keydown bubbles to the host alongside the button's click).
74
+ const path = e.composedPath();
75
+ if (path.some((n) => n.classList?.contains("chip__remove"))) {
76
+ return;
77
+ }
78
+ if (e.key === "Enter" || e.key === " ") {
79
+ e.preventDefault();
80
+ this._activate();
81
+ }
82
+ };
83
+ this._onRemove = (e) => {
84
+ e.stopPropagation();
85
+ if (this.disabled)
86
+ return;
87
+ this.dispatchEvent(new CustomEvent("xm-chip-remove", {
88
+ detail: { value: this.value },
89
+ bubbles: true,
90
+ composed: true,
91
+ }));
92
+ };
93
+ }
94
+ get _labelText() {
95
+ return (this.textContent ?? "").trim();
96
+ }
97
+ get _hasIconLeft() {
98
+ for (const node of this.childNodes) {
99
+ if (node.nodeType === Node.ELEMENT_NODE &&
100
+ node.getAttribute("slot") === "icon-left") {
101
+ return true;
102
+ }
103
+ }
104
+ return false;
105
+ }
106
+ connectedCallback() {
107
+ super.connectedCallback();
108
+ if (!this.hasAttribute("tabindex") && !this.disabled)
109
+ this.tabIndex = 0;
110
+ this.addEventListener("click", this._onHostClick);
111
+ this.addEventListener("keydown", this._onHostKeydown);
112
+ if (!this._mo) {
113
+ this._mo = new MutationObserver(() => this.requestUpdate());
114
+ this._mo.observe(this, { childList: true, characterData: true, subtree: true });
115
+ }
116
+ }
117
+ disconnectedCallback() {
118
+ super.disconnectedCallback();
119
+ this.removeEventListener("click", this._onHostClick);
120
+ this.removeEventListener("keydown", this._onHostKeydown);
121
+ this._mo?.disconnect();
122
+ this._mo = null;
123
+ }
124
+ updated() {
125
+ // Reflect interactive semantics onto the host so a parent group's
126
+ // roving focus + a screen reader both see one consistent control.
127
+ this.setAttribute("role", "button");
128
+ this.setAttribute("aria-pressed", this.selected ? "true" : "false");
129
+ if (this.disabled) {
130
+ this.setAttribute("aria-disabled", "true");
131
+ this.tabIndex = -1;
132
+ }
133
+ else {
134
+ this.removeAttribute("aria-disabled");
135
+ }
136
+ }
137
+ _activate() {
138
+ if (this.disabled)
139
+ return;
140
+ const ev = new CustomEvent("xm-chip-activate", {
141
+ detail: { value: this.value },
142
+ bubbles: true,
143
+ composed: true,
144
+ cancelable: true,
145
+ });
146
+ const proceed = this.dispatchEvent(ev);
147
+ // No group owned it (not prevented) → self-toggle for standalone use.
148
+ if (proceed)
149
+ this.selected = !this.selected;
150
+ }
151
+ render() {
152
+ const cls = [
153
+ "chip",
154
+ `chip--${this.size}`,
155
+ this.selected && "chip--selected",
156
+ this.disabled && "chip--disabled",
157
+ this.category && `chip--category-${this.category}`,
158
+ ]
159
+ .filter(Boolean)
160
+ .join(" ");
161
+ return html `
162
+ <link rel="stylesheet" href="${CHIP_CSS}" />
163
+ <style>
164
+ :host {
165
+ display: inline-flex;
166
+ vertical-align: middle;
167
+ outline: none;
168
+ }
169
+ :host([hidden]) {
170
+ display: none;
171
+ }
172
+ /* The focus ring renders on the inner .chip when the host is
173
+ focused, so the pill itself shows the 3px halo. */
174
+ :host(:focus-visible) .chip {
175
+ box-shadow: var(--xm-state-focus-ring);
176
+ }
177
+ </style>
178
+ <div class="${cls}">
179
+ <span class="chip__icon ${this._hasIconLeft ? "" : "is-empty"}"
180
+ ><slot name="icon-left" @slotchange=${this._onSlot}></slot
181
+ ></span>
182
+ <span class="chip__label"><slot></slot></span>
183
+ ${this.removable
184
+ ? html `<button
185
+ type="button"
186
+ class="chip__remove"
187
+ aria-label="Remove ${this._labelText || this.value}"
188
+ ?disabled=${this.disabled}
189
+ @click=${this._onRemove}
190
+ >
191
+ <svg
192
+ viewBox="0 0 24 24"
193
+ width="12"
194
+ height="12"
195
+ fill="none"
196
+ stroke="currentColor"
197
+ stroke-width="2.4"
198
+ stroke-linecap="round"
199
+ stroke-linejoin="round"
200
+ aria-hidden="true"
201
+ >
202
+ <path d="M6 6l12 12M18 6L6 18" />
203
+ </svg>
204
+ </button>`
205
+ : nothing}
206
+ </div>
207
+ `;
208
+ }
209
+ };
210
+ __decorate([
211
+ property({ type: String })
212
+ ], XmChip.prototype, "value", void 0);
213
+ __decorate([
214
+ property({ type: String })
215
+ ], XmChip.prototype, "size", void 0);
216
+ __decorate([
217
+ property({ type: Boolean, reflect: true })
218
+ ], XmChip.prototype, "selected", void 0);
219
+ __decorate([
220
+ property({ type: Boolean })
221
+ ], XmChip.prototype, "removable", void 0);
222
+ __decorate([
223
+ property({ type: Boolean, reflect: true })
224
+ ], XmChip.prototype, "disabled", void 0);
225
+ __decorate([
226
+ property({ type: String })
227
+ ], XmChip.prototype, "category", void 0);
228
+ XmChip = __decorate([
229
+ customElement("xm-chip")
230
+ ], XmChip);